20

我正在构建一个存储库,我在很多地方都看到了两个不将 IQueryable 暴露在存储库之外的原因。

1) 首先是因为不同的 LINQ 提供程序可能表现不同,并且这种差异应该包含在存储库中。

2) 二是防止服务级别开发人员修改数据库查询,从而意外导致性能问题。

我想问题 2 只能通过将所有查询逻辑保留在存储库中并且不允许任何形式的外部查询构建来防止?但这对我来说似乎有点不切实际。

问题 1 似乎可以通过使用数据对象模式来解决。

例如public IEnumerable<T> FindBy(Query query)

我的问题是,为什么我不只是传递一个 lambda 表达式,因为它独立于提供程序,似乎会为我提供与查询对象相同的功能和相同级别的分离?

例如public IEnumerable<T> FindBy(Expression<Func<T,bool>> predicate)

有什么理由不这样做吗?它是否违反了某些规则?最佳实践?我应该知道什么?

4

1 回答 1

24

只需返回一个IQueryable<T>.

在你再写一点“存储库代码”之前,你会从阅读 Ayende 的文章Architecting in the Pit of Doom - The Evils of the Repository Abstraction Layer 中受益匪浅

毫无疑问,您的方法会增加不必要的复杂性。

Generic List of OrderBy Lambda中其他问题的所有代码除了用不必要和不熟悉的抽象来掩盖现有的有效 API 之外,没有做任何事情。

关于你的两个担忧,

  1. LINQ 提供程序的行为确实不同,但只要您传递的谓词可以由 LINQ 提供程序处理,这无关紧要。否则,您仍然会遇到同样的问题,因为您传递的是 an ,无论如何Expression它最终都会传递给。IQueryable如果IQueryProvider实现不能处理你的谓词,那么它就不能处理你的谓词。ToList()(如果您需要在无法翻译的进一步过滤之前进行评估,您可以随时调用)。

  2. 修改查询可能会导致性能问题,但更可能会暴露急需的功能。此外,次优 LINQ 查询引起的性能问题可能比拉取比您需要的多得多的记录所引起的性能问题的危害要小得多,以避免暴露IQueryable或系统地过滤任何数据访问逻辑通过臃肿实际上不做任何事情的抽象级别(第一个威胁更重要)。通常,这不会成为问题,因为大多数领先的 LINQ 提供程序都会在翻译过程中优化您的查询逻辑。

如果您想从前端隐藏查询逻辑,请不要尝试创建通用存储库。用实际的业务特定方法封装查询。现在,我可能弄错了,但我假设您对存储库模式的使用受到领域驱动设计的启发。如果是这种情况,那么使用存储库的原因是允许您创建一个主要关注域模型的持久性无知域。但是,使用这种通用存储库只会将您的语义从 更改Create Read Update DeleteFind Add Remove Save. 那里没有嵌入任何真正的商业知识。

考虑一个项目的意义(和可用性)

interface IPersonRepository 
{ 
     Person GetById(int id);
     IEnumerable<Person> FindByName(string firstName, string lastName);
}

interface IRepository<T> { 
     IEnumerable<T> FindBy(Query<T> query);
}

此外,您实际上可以指出使用IRepository<T>at 的好处(而不是 a IQueryable<T>)吗?

此外,考虑到使用通用方法,您实际上根本没有封装查询逻辑。你最终在外部构建它,这将导致更多额外的不必要的代码。

*关于建议不要使用的资源的另一个注意事项IQueryable<T>是,值得查看它们的发布日期。曾经有一段时间,LINQ 提供程序的可用性非常有限(对于早期的 EF 和 LINQ-to-SQL)。那时,暴露一个IQueryable<T>将导致与 Microsoft ORM 的一些更流行的替代品不兼容(LINQ-to-NHibernate 早已实现)。目前,LINQ 支持在严肃的 ORM .NET 库中几乎无处不在

于 2012-08-16T03:20:54.527 回答