我应该从我的数据访问层返回IEnumerable<T>还是IQueryable<T>?

38

我知道这可能是个看法问题,但我正在寻找最佳实践。

据我了解,IQueryable<T> 实现了 IEnumerable<T>,因此在我的数据访问层(DAL)中,我当前有以下方法签名:

IEnumerable<Product> GetProducts();
IEnumerable<Product> GetProductsByCategory(int cateogoryId);
Product GetProduct(int productId);

在这里,我应该使用 IQueryable<T> 吗?

两种方法的利弊是什么?

请注意,我打算使用Repository模式,所以我会有一个类似于以下的类:

public class ProductRepository {

    DBDataContext db = new DBDataContext(<!-- connection string -->);

    public IEnumerable<Product> GetProductsNew(int daysOld) {
        return db.GetProducts()
          .Where(p => p.AddedDateTime > DateTime.Now.AddDays(-daysOld ));
    }
}

我应该将我的IEnumerable<T>更改为IQueryable<T>吗?这两者之间有什么优缺点?

4个回答

51

这取决于您想要什么行为。

  • 返回IList<T>告诉调用者他们已经接收到了所有请求的数据
  • 返回IEnumerable<T>告诉调用者他们需要迭代结果,并且它可能是按需加载的。
  • 返回IQueryable<T>告诉调用者结果由 Linq 提供程序支持,可以处理某些类别的查询,将负担放在调用者身上以形成高效的查询。

尽管后者(假设您的存储库完全支持它)为调用者提供了很大的灵活性,但它是最难测试和可能是最不确定的。


没错。我一直把IList和IEnumerable归为同一类,但我更喜欢这个想法。 - scottm

8

还有一件需要考虑的事情:你的分页/排序支持在哪里?如果你在仓库中提供了分页支持,返回 IEnumerable<T> 就可以了。如果你在仓库之外进行分页(比如在控制器或服务层),那么你真正想使用的是 IQueryable<T>,因为你不希望在分页之前将整个数据集加载到内存中。


5
巨大的差异。我经常看到这种情况。
在命中数据库之前先建立IQueryable。只有在调用急切函数(.ToList()为例)或您确实尝试提取值时,IQueryable才会命中DB。IQueryable = 惰性。
IEnumerable会立即执行您的lambda对DB的操作。IEnumerable = 急切。
至于在Repository模式中使用哪种,我认为是急切的。我通常看到传递的是ILists,但需要其他人为您解决这个问题。编辑-通常会看到IEnumerable而不是IQueryable,因为您不希望超出Repository A层确定何时发生数据库连接,或者B层在Repository之外添加任何联接逻辑。
有一个非常好的LINQ视频,我非常喜欢它-它不仅涉及IEnumerable v IQueryable,还具有一些奇妙的见解。 http://channel9.msdn.com/posts/matthijs/LINQ-Tips-Tricks-and-Optimizations-by-Scott-Allen/

只是为了清楚起见,ProductRepository 位于我的应用程序内...而 DAL 是一个单独的项目,我有一个引用。因此,我从我的应用程序调用存储库,存储库调用 DAL 项目中的 DBDataContext 类(其中包含所有通用方法),然后进行任何针对数据库的 LinqToSQL 操作。 - Armstrongest
3
IEnumerable 并不一定是急切执行的。Enumerable.Select 返回一个惰性的 IEnumerable。 - Amy B

3
您可以使用IQueryable并接受某人可能创建 SELECT N + 1的情况。这是一个缺点,还有一个事实,您可能会在存储库上面的层中最终得到特定于存储库实现的代码。优点是,您允许委托常见操作(如分页和排序)在存储库之外表达,因此减轻了它的负担。如果需要将数据与其他数据库表连接,则更加灵活,因为查询将保持为表达式,因此可以在解析为查询并命中数据库之前添加它。
另一种选择是锁定存储库,以便通过调用ToList()返回材料化列表。对于分页和排序的示例,您需要将skip、take和sort expression作为参数传递给存储库的方法,并使用参数仅返回结果窗口。这意味着存储库正在承担分页和排序以及数据投影的责任。
这是一个有点主观的判断,您是否要赋予应用程序linq的能力,并在存储库中减少复杂性,还是控制数据访问。对我来说,这取决于与每个实体相关的查询数量,实体组合以及我想管理该复杂性的位置。

嗯... 有些事情需要考虑。 - Armstrongest

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接