存储库/IQueryable/查询对象

问题描述 投票:0回答:1

我正在构建一个存储库,并且我在很多地方看到了不要在存储库之外公开 IQueryable 的 2 个原因。

  1. 第一个是因为不同的 LINQ 提供程序可能会表现出不同的行为 有所不同,并且这种差异应该包含在 存储库。
  2. 第二个是防止服务级别开发者修改 数据库查询意外导致性能问题。

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

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

例如

public IEnumerable<T> FindBy(Query query)

我的问题是,为什么我不直接传递一个 lambda 表达式,因为它与提供者无关,似乎为我提供了与查询对象相同的功能以及相同的分离级别?

例如

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

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

c# repository-pattern iqueryable objectquery
1个回答
26
投票

只需返回
IQueryable<T>

在编写另一段“存储库代码”之前,阅读 Ayende 的文章 Doom 坑中的架构 - 存储库抽象层的邪恶

将使您受益匪浅

毫无疑问,你的方法明显增加了不必要的复杂性。

来自 OrderBy Lambda 的通用列表的其他问题中的所有代码除了使用不必要且不熟悉的抽象来掩盖现有的有效 API 之外,无法执行任何其他操作。

关于您关心的两个问题,

  1. LINQ 提供程序的行为确实有所不同,但只要 LINQ 提供程序可以处理您传递的谓词,这就无关紧要。否则,您仍然会遇到同样的问题,因为您传递的是

    Expression
    ,无论如何它最终都会传递到
    IQueryable
    。如果
    IQueryProvider
    实现无法处理您的谓词,那么它也无法处理您的谓词。 (如果您需要在无法翻译的进一步过滤之前进行评估,您可以随时调用
    ToList()
    )。

  2. 修改查询可能会导致性能问题,但更有可能暴露急需的功能。此外,与为避免暴露

    IQueryable
    而拉取超出所需数量的记录或系统地过滤任何数据访问所带来的性能问题相比,次优 LINQ 查询所引起的性能问题的危害可能要小得多通过臃肿的抽象层次来逻辑,但实际上并没有做任何事情(第一个威胁更重要)。一般来说,这不会成为问题,因为大多数领先的 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>
会导致与一些 Microsoft ORM 更流行的替代品不兼容(LINQ-to-NHibernate 早已实现)。目前,LINQ 支持在重要的 ORM .NET 库中几乎无处不在

© www.soinside.com 2019 - 2024. All rights reserved.