要返回IQueryable <T>还是不返回IQueryable <T>


73

我有一个存储库类,将我的LINQ包装到SQL数据上下文中。存储库类是业务线类,其中包含所有数据层逻辑(以及缓存等)。

这是我的repo界面的v1。

public interface ILocationRepository
{
    IList<Location> FindAll();
    IList<Location> FindForState(State state);
    IList<Location> FindForPostCode(string postCode);
}

但是为了处理FindAll的分页,我正在辩论是否公开IQueryable <ILocation>而不是IList来简化诸如分页之类的接口。

从数据存储库中公开IQueryable的利弊是什么?

很感谢任何形式的帮助。


8
如何在存储库界面中使用IEnumerable <T>?
康斯坦丁·塔尔库斯

@KonstantinTarkus我不建议IEnumerable<T>在存储库或BLL中使用它,因为如果您不注意,它可能会影响性能,并且在这种情况下您会GetEnumerator()多次调用同一目录的许多foreach循环。
Wahid Bitar 2014年

Answers:


90

优点;可组合性:

  • 来电者可以添加过滤器
  • 呼叫者可以添加分页
  • 呼叫者可以添加排序
  • 等等

缺点 不可测试性:

  • 您的存储库不再可以进行单元测试。您不能依赖a:它起作用,b:它做什么
    • 调用者可以添加一个不可翻译的函数(即没有TSQL映射;在运行时中断)
    • 呼叫者可以添加一个过滤器/排序,使其性能像狗一样
  • 由于调用者期望IQueryable<T>是可组合的,因此它排除了不可组合的实现-或迫使您为其编写自己的查询提供程序
  • 这意味着您无法优化/分析DAL

为了稳定起见,我已选择公开IQueryable<T>不在Expression<...>自己的存储库中。这意味着我知道存储库的行为方式,并且我的上层可以使用模拟,而不必担心“实际的存储库是否支持此操作?” (强制进行集成测试)。

我仍然使用IQueryable<T>内部信息库-但不下来的边界。我在这里对这个主题发表了更多想法。将分页参数放在存储库界面上也很容易。您甚至可以使用扩展方法(在接口上)添加可选的分页参数,以便具体类仅可以实现1个方法,但调用者可以使用2或3个重载。


5
如果使用得当,则IEnumerable<T>可以-但主要用于大数据。对于常规查询,我希望使用封闭集(数组/列表/等)。特别是,我目前正在使用MVC,我希望控制器获取所有数据-不要将其推迟到视图之前-否则您将无法完全测试...
Marc Gravell

2
...该控制器,因为您尚未实际证明它正在获取数据(因为IEnumerable<T>通常与延迟执行一起使用)。如果回购收益的IList <T>(或类似),那么你知道你已经得到的数据。
Marc Gravell

1
@Mark和..在单元测试中触发可执行文件有什么问题?例如:Assert.IsTrue(result.Any())
Konstantin Tarkus,2009年

2
使用IQueryable <T>可以更改实际查询。使用IEnumerable <T>时,要少一些-但仍然:获取数据是控制器的责任,而不是视图的责任。
Marc Gravell

1
关于您的有关可选分页参数以及调用方可以使用2或3个重载的最后一条语句,您能否指出我们有关此的文章?
肖恩·麦克林

7

正如前面的答案所提到的,公开IQueryable使调用者可以使用IQueryable本身进行播放,这将变得危险。

封装业务逻辑的首要职责是维护数据库的完整性。

您可以继续公开IList,并可能如下更改参数,这就是我们正在做的...

public interface ILocationRepository
{
    IList<Location> FindAll(int start, int size);
    IList<Location> FindForState(State state, int start, int size);
    IList<Location> FindForPostCode(string postCode, int start, int size);
}

如果size == -1,则全部返回...

替代方法...

如果仍要返回IQueryable,则可以在函数内部返回List的IQueryable。例如。

public class MyRepository
{
    IQueryable<Location> FindAll()
    {
        List<Location> myLocations = ....;
        return myLocations.AsQueryable<Location>;
        // here Query can only be applied on this
        // subset, not directly to the database
    }
}

第一种方法比内存有一个优势,因为您将返回较少的数据而不是全部。


1
这不是那么优雅。这样,CVertex必须将这些参数(开始,大小)添加到他创建的每个存储库方法中,这是非常糟糕的编程习惯。
09年

好于公开可以修改和破坏您的数据的数据库接口。
Akash Kava

只需要在实际使用的位置添加分页参数。而且,如果使用它们,则不会浪费精力。
Frank Schwieterman 09年

2
uch,您的第二种方法将成为应用杀手!它将所有结果拉入内存,然后对它们执行LINQ。我从来没有发现过AsQueryable诚实的真实理由。我什至不确定为什么它存在。它不会突然将您的List变成IQueryable,并允许您构建发送到数据存储的表达式树,这又意味着什么呢?
2012年

1
AsQueryable当您IEnumerable从旧版API获得非泛型时,@ AlexFord很有用。
joshperry13年

2

我建议您使用IEnumerable代替IList,这样您将具有更大的灵活性。

这样,您就可以从Db中仅获取您真正要使用的那部分数据,而无需在存储库中完成额外的工作。

样品:

// Repository
public interface IRepository
{
    IEnumerable<Location> GetLocations();
}

// Controller
public ActionResult Locations(int? page)
{
    return View(repository.GetLocations().AsPagination(page ?? 1, 10);
}

超级干净简单。


为什么?权衡是什么?
理查德

1
如果要向用户显示分页列表,则IList <Location> FindAll()效率不是很高(假设将从db中提取1000行,但仅显示其中的10行)。使用IQueryable,您将不会遇到此类问题。
Konstantin Tarkus,2009年

顺便说一句,如果您要实现分页功能,请查看MvcContrib = codeplex.com/mvccontrib(MvcContrib.Pagination名称空间)中的现有帮助器类
Konstantin Tarkus,2009年

我喜欢创建详细的存储库方法。例如GetRecords(int page, int itemsPerPage)。而不是将分页传递给控制器​​。
2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.