我正在构建一个存储库,我在很多地方都看到了 2 个不在存储库之外公开 IQueryable 的理由。
- 首先是因为不同的 LINQ 提供程序可能运行不同,这种差异应该包含在存储 库。
- 第二种是防止服务级别开发人员修改数据库查询,以免意外导致性能问题。
我想问题 2 只能通过将所有查询逻辑保留在存储库中并不允许任何形式的外部查询构建来预防?但这对我来说似乎有点不切实际。
问题 1 似乎可以通过使用数据对象模式来解决。
例如 public IEnumerable<T> FindBy(Query query)
我的问题是,为什么我不直接传入一个 lambda 表达式,因为它与提供程序无关,似乎为我提供了与查询对象相同的功能,以及相同的分离级别?
例如 public IEnumerable<T> FindBy(Expression<Func<T,bool>> predicate)
有什么理由不这样做吗?它是否违反了一些规则?最佳实践?我应该知道的?
只需返回一个IQueryable<T>
。
在你再写一点"存储库代码"之前,阅读Ayende的文章Architecting in the Pit of Doom - The Evils of the Repository Abstraction Layer将受益匪浅
。毫无疑问,您的方法增加了大量不必要的复杂性。
OrderBy Lambda 泛型列表中另一个问题中的所有代码除了使用不必要和不熟悉的抽象屏蔽现有的有效 API 之外,无法执行任何其他操作。
关于你的两个关切,
- LINQ 提供程序
的行为确实不同,但只要 LINQ 提供程序可以处理要传递的谓词,这就无关紧要。 否则,您仍然会遇到相同的问题,因为您正在传递一个
Expression
,无论如何最终都会传递给IQueryable
。 如果IQueryProvider
实现无法处理谓词,则它无法处理谓词。 (如果需要在无法翻译的进一步筛选之前进行评估,则始终可以调用ToList()
)。修改查询可能会导致性能问题,但更有可能公开急需的功能。 此外,次优 LINQ 查询引起的性能问题可能比拉取比您需要的多得多的记录以避免公开
IQueryable
或通过实际上不执行任何操作的臃肿抽象级别系统地筛选任何数据访问逻辑所产生的性能问题危害要小得多(第一个威胁更为严重)。 通常,这不会成为问题,因为大多数领先的 LINQ 提供程序都会在转换过程中优化查询逻辑。
如果要在前端隐藏查询逻辑,请不要尝试创建通用存储库。 使用实际业务特定方法封装查询。 现在,我可能弄错了,但我假设您对存储库模式的使用受到领域驱动设计的启发。 如果是这种情况,那么使用存储库的原因是允许您创建一个忽略持久性的域,主要关注域模型。 但是,使用这种通用存储库只会将您的语义从Create Read Update Delete
更改为Find 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>
的好处(而不是IQueryable<T>
)?
此外,请考虑使用通用方法,实际上根本不封装查询逻辑。 你最终在外部构建它,这将导致更多额外的不必要的代码。
*关于建议不要使用IQueryable<T>
的资源的另一个注意事项是,值得查看它们的发布日期。 曾经有一段时间,LINQ 提供程序的可用性非常有限(早期的 EF 和 LINQ to-SQL)。 那时,公开IQueryable<T>
将导致与ORM的一些更流行的替代品不兼容Microsoft(LINQ到NHibernate早已实现)。 在这个时间点,LINQ支持在严肃的ORM .NET库中几乎无处不在。