实体框架存储库模式为什么不返回Iqueryable?

时间:2015-11-17 11:14:09

标签: entity-framework repository-pattern

有几个很好的博客关于如何使用泛型类实现存储库模式和工作单元模式。

Implementing a Data Access Layer with Entity Framework 6.1

Implementing the Repository and Unit of Work Patterns

理念是,定义一个通用接口IRepository和一个隐藏数据实际访问方式的类库。可以使用Entity Framework DbContext访问它,也可以使用存储库作为内存集合进行单元测试。

public interface public interface IRepository<T> where T : class
{
    T GetById(int Id);
    void DeleteById(int Id);

    void Add(T entity);
    void Update(T entity);

    etc.
}

我经常看到添加了几个类似于Queryable和/或Enumerable函数的Query函数。

例如在Implementing a data access layer我看到:

/// Returns an IEnumerable based on the query, order clause and the properties included
/// <param name="query">Link query for filtering.</param>
/// <param name="orderBy">Link query for sorting.</param>
/// <param name="includeProperties">Navigation properties seperated by comma for eager loading.</param>
/// <returns>IEnumerable containing the resulting entity set.</returns>
IEnumerable<T> GetByQuery(Expression<Func<T, bool>> query = null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null, string includeProperties = "");

/// <summary>
/// Returns the first matching entity based on the query.
/// </summary>
/// <param name="predicate"></param>
/// <returns></returns>
T GetFirst(Expression<Func<T, bool>> predicate);

如果接口有一个函数IQueryable GetQuery(),那么我就不必像GetFirst()和GetByQuery()这样的函数。

  

问题:为什么不推荐这个?人们可以以不合需要的方式更改数据吗?

3 个答案:

答案 0 :(得分:4)

我们使用存储库模式的原因之一是封装胖查询。这些查询使得在ASP.NET MVC控制器中难以阅读,理解和测试操作。此外,随着应用程序的增长,您在多个位置重复胖查询的可能性也会增加。使用存储库模式,我们将这些查询封装在存储库类中。结果是更轻薄,更清洁,更易于维护且更易于测试。考虑这个例子:

var orders = context.Orders
    .Include(o => o.Details)
        .ThenInclude(d => d.Product)
    .Where(o => o.CustomerId == 1234);

这里我们直接使用没有存储库模式的DbContext。当您的存储库方法返回IQueryable时,其他人将获得IQueryable并在其上构建查询。结果如下:

var orders = repository.GetOrders()
    .Include(o => o.Details)
        .ThenInclude(d => d.Product)
    .Where(o => o.CustomerId == 1234);

您能看到这两个代码段之间的区别吗?唯一的区别在于第一行。在第一个例子中,我们使用context.Orders,在第二个例子中我们使用repository.GetOrders()。那么,这个存储库解决了什么问题?没事!

您的存储库应该返回域对象。因此,GetOrders()方法应返回IEnumerable。有了这个,第二个例子可以重写为:

var orders = repository.GetOrders(1234);

看到区别? 取自Hamedani先生blog

答案 1 :(得分:3)

不建议这样做,因为它会使存储库模式无效。 此模式的目的是通过抽象将DAL实现与其他项目分开。

本质上,返回IQueryable将返回TSQL语句而不是结果,这意味着任何引用DAL的项目都需要对EF进行额外的引用才能执行查询。这种“数据泄露”会使您的项目更加紧密,从而与相关原则的分离相矛盾。

您可以在此处详细了解存储库模式及其优势: http://www.codeproject.com/Articles/526874/Repositorypluspattern-cplusdoneplusright

答案 2 :(得分:-2)

除了其他答案之外,我想说要实现 IQueryable 查询结果,您必须使用特定于实现的扩展方法,例如EntityFrameworkQueryableExtensions.ToListAsync

我发现传递表达式的方法可以更好地保留抽象并且非常接近于直接使用 IQueryable。但是,它也有一些限制。例如。如果你想使用实现或提供者特定的特性,比如 UDF 甚至内置函数(EF.Functions.Like),很容易泄露存储库抽象之上的这些细节(但我们应该对抗它并在存储库)。

对于所有其他情况,为了避免重复一些常见和繁琐的查询,您可以为存储库接口创建一组扩展方法,这将隐藏在不言自明的名称下的查询。