Entity Framework仓储模式为什么不返回IQueryable?

16

有几篇很好的博客介绍如何使用通用类一起实现仓储模式和工作单元模式。

使用Entity Framework 6.1实现数据访问层

实现仓储和工作单元模式

这个想法是定义一个通用接口IRepository和一个隐藏了数据实际访问方式的类Repository。它可以使用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函数的查询功能。

例如,在实现数据访问层中,我看到:

/// 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()这样的函数了。
问题:为什么不推荐这样做?人们能以不良的方式更改数据吗?
2个回答

9
这并不推荐,因为这会使仓库模式无效。 该模式的目的是通过抽象来保持您的数据访问层实现与其他项目的分离。
从本质上讲,返回IQueryable将返回TSQL语句而不是结果,这意味着任何引用您的数据访问层的项目都需要额外引用EF才能执行查询。这种“数据泄漏”会使您的项目更加紧密,从而违反了关注点分离原则。
您可以在这里阅读有关存储库模式及其优势的更多信息: http://www.codeproject.com/Articles/526874/Repositorypluspattern-cplusdoneplusright

3

我们使用存储库模式的原因之一是为了封装复杂查询。这些查询使得在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先生处检索博客得到


15
嘿,这是从这篇博客中复制粘贴的文本:https://programmingwithmosh.com/entity-framework/common-mistakes-with-the-repository-pattern/。你应该提到这个信息是“借用”的 -))) - Vitaliy Markitanov
4
我认为你忽略了第一个版本和第二个版本中的一个重要方面。第一个版本需要用户“知道”上下文是什么,这意味着它依赖于例如EF6之类的内容。而第二个版本只需要知道什么是IEnumerable。 - Felix Castor

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