存储库应该返回IQueryable吗?


66

我已经看到很多项目的存储库返回的实例IQueryable。这允许其他过滤器,并且可以IQueryable通过其他代码对进行排序,从而转换为正在生成的不同SQL。我很好奇这种模式的来源以及它是否是一个好主意。

我最大的担心是,一个IQueryable被枚举的承诺会在一段时间后打入数据库。这意味着将向存储库外部抛出错误。这可能意味着在应用程序的不同层中引发了Entity Framework异常。

过去,我也遇到过多个活动结果集(MARS)的问题(尤其是在使用事务时),这种方法听起来更容易导致这种情况的发生。

我总是在每个LINQ表达式的末尾调用AsEnumerableToArray,以确保在离开存储库代码之前已命中数据库。

我想知道返回IQueryable是否可用作数据层的构建块。我已经看到一些相当奢侈的代码,其中一个存储库调用另一个存储库以构建更大的代码IQueryable


如果在延迟查询执行时数据库不可用与查询构造时数据库不可用,会有什么区别?无论如何,使用代码都必须处理这种情况。
史蒂文·埃弗斯

有趣的问题,但可能很难回答。一个简单的搜索显示有关您的问题的激烈辩论:duckduckgo.com/?q=repository+return+iqueryable
Corbin

6
一切取决于您是否要允许客户添加可从延迟执行中受益的Linq子句。如果他们where在deferred中添加了子句IQueryable,则只需通过网络发送数据,而不是整个结果集。
罗伯特·哈维

如果使用的是存储库模式-否,则不应返回IQueryable。这是一个很好的原因
2013年

1
@SteveEvers有很大的不同。如果在我的存储库中引发了异常,则可以使用特定于数据层的异常类型将其包装。如果以后发生,我必须在那里捕获原始异常类型。我离异常源越近,就越有可能知道引起异常的原因。这对于创建有意义的错误消息很重要。恕我直言,允许EF特定的异常离开我的存储库是对封装的违反。
特拉维斯公园公园

Answers:


47

返回IQueryable肯定会为存储库的使用者提供更大的灵活性。它把缩小结果范围的责任交给了客户,这自然可以既是好处,又是拐杖。

从好的方面来说,您无需创建大量的存储库方法(至少在此层上)— GetAllActiveItems,GetAllNonActiveItems等即可获取所需的数据。同样,根据您的喜好,这可能是好是坏。您(/应该)定义您的实现所遵循的行为契约,但这取决于您。

因此,您可以将坚韧不拔的检索逻辑放在存储库之外,并根据用户需要使用它。因此,公开IQueryable可以提供最大的灵活性,并且可以实现与内存中过滤等相反的高效查询,并且可以减少制作大量特定数据提取方法的需求。

另一方面,现在您已经为用户提供了shot弹枪。他们可以执行您可能不希望做的事情(过度使用.include(),在各自的实现中执行繁重的查询以及执行内存中过滤等),这基本上会避开分层和行为控件,因为您已经给出了完全访问权限。

因此,取决于团队,他们的经验,应用程序的大小,整体分层和体系结构……这取决于:-\


请注意,如果您想为其工作,则可以返回IQueryable,该IQueryable依次调用DB,但添加了分层和行为控件。
psr 2013年

1
@psr您能解释一下这是什么意思吗?
特拉维斯公园公园

1
@TravisParks-IQueryable不必由数据库实现,您可以自己编写IQueryable类-这非常困难。因此,您可以围绕数据库IQueryable编写IQueryable包装器,并让IQueryable限制对基础数据库的完全访问权限。但这非常困难,以至于我不希望它能被广泛实现。
psr

2
请注意,即使您不返回IQueryable,也可以通过简单地将a传递Expression<Func<Foo,bool>>给您的方法来避免不得不创建许多方法(GetAllActiveItems,GetAllNonActiveItems等)。此参数可以直接传递给Where方法,从而使使用者可以选择其过滤器而无需直接访问IQueryable <Foo>。
平坦

1
@hanzolo:取决于您对重构的含义。的确,当您具有分层的和松散耦合的代码库时,更改设计(例如添加新字段或更改关系)会更加痛苦。但是当您只需要更改一层时,重构就不那么麻烦了。这完全取决于您是否希望重新设计应用程序,还是只是重构同一基本体系结构的实现。无论哪种情况,我仍然主张松散耦合,但是您没错,它在更改核心领域概念时会增加重构。
平坦

29

将IQueryable暴露给公共接口不是一个好习惯。原因是根本的:不能像据说的那样提供IQueryable实现。

公共接口是提供者和客户之间的合同。在大多数情况下,都有望得到全面实施。IQueryable是一个骗子:

  1. IQueryable几乎不可能实现。目前没有适当的解决方案。甚至实体框架也有很多UnsupportedExceptions
  2. 如今,可查询提供程序对基础结构的依赖性很大。Sharepoint有其自己的部分实现,而My SQL提供程序则具有不同的部分实现。

存储库模式通过分层(最重要的是实现的独立性)为我们提供了清晰的表示形式。因此,我们可以更改将来的数据源。

问题是“ 是否应该替换数据源? ”。

如果明天可以将部分数据移至SAP服务,NoSQL数据库或仅移至文本文件,我们可以保证IQueryable接口的正确实现吗?

(有关为什么这是不好的做法的更多优点)


如果不咨询易变的外部链接,这个答案就不会独立存在。考虑至少总结一下外部资源所提出的要点,并尝试将个人观点排除在答案之外(“我所听到的最糟糕的主意”)。还可以考虑删除似乎与任何IQueryable都不相关的代码段。
拉尔斯·维克伦德2013年

1
谢谢@LarsViklund,答案已通过示例和问题描述进行了更新
Artru 2013年

19

实际上,如果要推迟执行,则有三种选择:

  • 这样做-暴露一个IQueryable
  • 实现一个结构,该结构公开用于特定过滤器或“问题”的特定方法。(GetCustomersInAlaskaWithChildren,下方)
  • 实现一个结构,该结构公开强类型的filter / sort / page API并在IQueryable内部进行构建。

我更喜欢第三个(尽管我也帮助同事实现了第二个),但是显然涉及一些设置和维护。(T4进行救援!)

编辑:要清除围绕我在说什么的混乱,请考虑以下示例,该示例从IQueryable偷来的可以杀死您的狗,偷走妻子,杀死您的生存意志等。

在这种情况下,您需要公开以下内容:

public class CustomerRepo : IRepo 
{ 
     private DataContext ct; 
     public Customer GetCustomerById(int id) { ... } 
     public Customer[] GetCustomersInAlaskaWithChildren() { ... } 
} 

我正在谈论的API将允许您公开一个方法,该方法可以GetCustomersInAlaskaWithChildren灵活地表达(或条件的任何其他组合),并且存储库会将其作为IQueryable执行并将结果返回给您。但是重要的是它在存储库层中运行,因此它仍然利用了延迟执行的优势。一旦获得结果,您仍然可以将LINQ的对象放在心上。

这种方法的另一个优点是,由于它们是POCO类,因此该存储库可以驻留在Web或WCF服务的后面。它可能会暴露给AJAX或其他不了解LINQ的其他调用者。


4
因此,您将构建查询API来替代.NET的内置查询API吗?
迈克尔·布朗

4
听起来对我来说毫无意义
13年

7
@MikeBrown这个问题的重点(也是我的答案)是,您希望在不暴露自身情况下暴露其行为优点。您将如何使用内置API来做到这一点?IQueryable IQueryable
GalacticCowboy

2
这是一个很好的重言式。您尚未说明为什么要避免这种情况。Heck WCF数据服务通过电线公开IQueryable。我不是要挑剔您,我只是不了解这种对IQueryable人们的厌恶情绪。
迈克尔·布朗

2
如果仅打算使用IQueryable,那么为什么还要使用存储库?存储库旨在隐藏实现细节。(此外,WCF数据服务比我过去四年中使用此类存储库研究的大多数解决方案都更新了很多。因此,不太可能在不久的将来对它们进行更新以使使用闪亮的新玩具。)
GalacticCowboy

8

确实只有一个合理的答案:这取决于存储库的使用方式。

在一个极端情况下,您的存储库是围绕DBContext的非常薄的包装程序,因此您可以在数据库驱动的应用程序周围注入可测试性。实际上,现实世界中没有人期望它可以在没有LINQ友好数据库的情况下以断开方式使用,因为您的CRUD应用程序将永远不需要它。然后确定,为什么不使用IQueryable?我可能更喜欢IEnumarable,因为它可以带来大多数好处(请参阅:延迟执行),并且不会觉得很脏。

除非您处于那种极端,否则我将尽力利用存储库模式的精神,并返回不涉及基础数据库连接的适当的物化集合。


6
关于return IEnumerable,重要的是要注意((IEnumerable<T>)query).LastOrDefault()query.LastOrDefault()(假设queryIQueryable)有很大的不同。因此,只有IEnumerable在您要遍历所有元素时才返回有意义。

7
 > Should Repositories return IQueryable?

NO,如果你想要做测试驱动开发/单元测试因为要创建一个模拟库从数据库或其他的IQueryable提供商测试孤立businesslogic(=仓库消费者)没有简单的方法。


6

从纯体系结构的角度来看,IQueryable是一个泄漏的抽象。通常,它为用户提供了太多功能。话虽这么说,但在某些地方有意义。如果您使用的是OData,则IQueryable可以非常轻松地提供易于过滤,可排序,可分组等的终结点。实际上,我更喜欢创建我的实体映射到的DTO,而IQueryable则返回DTO。这样,我就可以预先过滤数据,并且实际上仅使用IQueryable方法来允许客户端自定义结果。


6

我也面临这样的选择。

因此,让我们总结一下积极和消极的一面:

正面:

  • 灵活性。允许客户端仅使用一种存储库方法构建自定义查询绝对是灵活且方便的

负面因素:

  • 无法测试。(您实际上将在测试底层的IQueryable实现,通常是您的ORM。但是您应该测试逻辑)
  • 责任模糊。很难说,存储库的特定方法是做什么的。实际上,这是一种上帝的方法,可以在整个数据库中返回您喜欢的任何内容
  • 不安全的。对于此存储库方法可以查询的内容没有任何限制,在某些情况下,从安全角度来看,此类实现可能是不安全的。
  • 缓存问题(或一般而言,任何预处理或后处理问题)。例如,将所有内容缓存在应用程序中通常是不明智的。您将尝试缓存某些内容,这会导致数据库负载很大。但是,如果您只有一种存储库方法,客户端将使用该存储库方法对数据库发出几乎任何查询,该怎么办呢?
  • 记录问题。在存储库层记录一些内容将使您首先检查IQueryable以检查是否记录了此特定调用。

我建议不要使用这种方法。可以考虑创建一些规范并实现存储库方法,这些方法可以使用这些规范查询数据库。规范模式是封装一组条件的好方法。


5

我认为在开发的初始阶段,将IQueryable公开在您的存储库中是完全可以接受的。有一些UI工具可以直接与IQueryable一起使用,并可以处理排序,过滤,分组等。

话虽如此,我认为仅仅将库德文件暴露在存储库中并称其为一天是不负责任的。具有用于常见查询的有用操作和查询助手,使您可以集中查询的逻辑,同时还向存储库的使用者公开较小的界面。

对于项目的前几个迭代,将IQueryable公开暴露在资源库中可以加快增长。在第一个完整版本发布之前,我建议将查询操作设为私有或受保护,并将野生查询置于一个屋顶下。


4

我所看到的完成的事情(以及我自己实现的事情)如下:

public interface IEntity<TKey>
{
    TKey Id { get; set; }
}

public interface IRepository<TEntity, in TKey> where TEntity : IEntity<TKey>
{
    void Create(TEntity entity);
    TEntity Get(TKey key);
    IEnumerable<TEntity> GetAll();
    IEnumerable<TEntity> GetAll(Expression<Func<TEntity, bool>> expression);
    void Update(TEntity entity);
    void Delete(TKey id);
}

这允许您做的就是具有灵活性,IQueryable.Where(a => a.SomeFilter)可以在concreteRepo.GetAll(a => a.SomeFilter)不公开任何LINQy业务的情况下对仓库进行查询。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.