【问题标题】:Should a Repository return IEnumerable<T> , IQueryable<T> or List<T>? [closed]存储库应该返回 IEnumerable<T>、IQueryable<T> 还是 List<T>? [关闭]
【发布时间】:2011-11-12 02:39:55
【问题描述】:

我想让我的应用程序尽可能灵活,但不要让我的界面过于具体而让自己陷入困境。

存储库的最佳对象类型是什么? IEnumerable、IQueryable 还是 List?

我正在考虑使用的技术是

  • Azure 应用结构缓存

  • 实体框架 4.1

  • 可能是 Windows Server AppFabric

【问题讨论】:

    标签: entity-framework-4.1 repository appfabric azure-appfabric appfabric-beta-2


    【解决方案1】:

    这取决于您是否希望将来对实体执行任何查询,以及这些查询是否应该在内存中:

    • 如果有未来的查询并且数据库应该做这项工作,则返回 IQueryable。
    • 如果将来有查询并且要在内存中完成,则返回 IEnumerable。
    • 如果没有进一步的查询并且需要读取所有数据,则返回 IList、ICollection 等。

    【讨论】:

    • 在哪里可以根据 DAL 发出查询?我应该将 IQueryable 传递给控制器​​吗?观点?
    • 如果您使用IEnumerable,您仍然可以使查询在数据库中运行——IQueryable 的文档说它旨在实现查询提供程序,而不是进行这种区分。一旦查询数据库完成,我不喜欢使用 IEnumerable,因为使用集合类型可以确保您返回具体化的结果,而不是错误地返回未评估的查询。
    • 这类问题要么与性能调优有关,在这种情况下,应使用所有考虑的选项(以及选择的最高性能)来衡量性能,或者考虑到架构方面的考虑。在后者的情况下,我建议将 IQueryable 暴露给控制器是可以接受的(因为控制器对域对象的了解是可以的),但是视图/视图模型应该是愚蠢的并且不知道这些事情......但是这个只是我的意见!
    • @makerofthings7:在严格的分层架构中,不能在 DAL 之外的任何地方发出由数据库支持的查询。可以在任何地方进行内存查询以转换数据结构,但无论如何您都无法阻止它,因为可以在内存中查询所有集合。
    • 我不认为应该返回 IQueryable,我已经发布了一个 answer 来说明原因。
    【解决方案2】:

    我会说使用 IQueryable 构建您的 DAL,并传递它,确保您的对象上下文生命周期是请求。这样您将获得延迟执行的好处,但会面临数据库查询效率低下的风险。

    然后确保您对应用程序(或至少是最有可能获得流量的部分)进行性能测试并查看数据访问模式。在 DAL 中创建专门的方法来检索完全具体化的对象并将这些查询作为预编译查询。

    存储库接口示例如下

      public interface IDataContext
      {
            void Add<T>(T entity) where T : BaseEntity;
            void Delete<T>(T entity) where T : BaseEntity;
            IQueryable<T> Find<T>(Expression<Func<T, bool>> where) where T : BaseEntity;
       }
    

    BaseEntity 是我们所有类的基类,看起来,这个类没有映射到 DB 中的任何表

    public abstract class BaseEntity
    {
            public int Id { get; set; }
            public DateTime CreateDateTime { get; set; }
            public string CreateUser { get; set; }
            public DateTime ModDateTime { get; set; }
            public string ModUser { get; set; }
            public byte[] RowVersion { get; set; }
    }
    

    Expression&lt;Func&lt;T, bool&gt;&gt; 会将整个表达式传递给您的存储库,而不仅仅是 Func,因为 EF 处理表达式以生成 SQL 查询,一个典型的用法是

    ICollection<WFGroup> wgGroups = this.dataContext.Find<WFGroup>((w) => true).ToList();
    

    WFGroup 是从 BaseEntity 派生的类,我通常使用延迟加载和代理,并且不会将对象分离/附加到上下文。

    【讨论】:

    • 我正在使用需要物化查询的缓存服务。 IEnumerable 是我能找到的最佳公分母。这是否意味着我在 IQueryable DAL 之上有一层将所有内容展平为 IEunmerable?如果是,什么是?实体模型、视图还是视图模型?
    • IEnumerable 不能保证物化对象 IQueryable 也是 IEnumerable。在我的例子中,T 是实体,我们有一个存储库实现的接口 IDataContext,每个方法都是通用的 T,其中 T 被约束到我们的基础实体。
    • 很有趣,我知道它如何适用于 1:1 表到实体映射,但我很好奇您如何处理更复杂的事情,例如 1:Many 关系...stackoverflow.com/q/8095728/328397跨度>
    • 我用示例界面更新了答案,1:n 在实体模型中处理。这在 EF 4 中使用修改后的 t4 有点难以实现,但在 EF 4.2 中它轻而易举
    • 我认为这个答案现在已经过时并且是错误的建议。出于多种原因,IQueryable 不应被传递,其中一个原因是潜在的 DB 滥用是危险的,但更多与 SoC 有关。我已经发布了一个 answer 来解释我的意思,并参考了最近关于此事的一篇文章。
    【解决方案3】:

    您有多大可能需要从 DAL 返回 IEnumerable 的自定义实现(不是集合)? (要回答这个问题,请查看您之前的项目并计算您周围有多少个或yield returns。)

    如果答案是“不太”,我会返回 ICollection 甚至数组(如果您想防止查询结果被无意修改。)在紧要关头,如果您需要更改查询要使用自定义 IEnumerable“流式传输”结果,您始终可以让旧方法调用新方法并具体化结果以保持与旧客户端的兼容性。

    【讨论】:

      【解决方案4】:

      最近有一篇很好的文章 here 涵盖了这一点,即在“返回 IQueryable 的存储库”标题下。它是这样说的:

      我们使用存储库模式的原因之一是封装胖查询。这些查询使 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);
      

      你能看出这两个代码sn-ps的区别吗?唯一的区别在于第一行。在第一个示例中,我们使用 context.Orders,在第二个示例中,我们使用 repository.GetOrders()。那么,这个存储库解决了什么问题呢?没什么!

      您的存储库应返回域对象。因此,GetOrders() 方法应该返回一个 IEnumerable。有了这个,第二个例子可以重写为:

      var orders = repository.GetOrders(1234);

      看到区别了吗?

      因此,我在团队中添加了以下编码约定:

      对于存储库类方法,永远不要返回 IQueryable 对象。始终先枚举或转换它(例如,ToArrayToListAsEnumerable)。

      原因是 IQueryable 将允许调用者在此基础上构建并最终修改在数据库上执行的 SQL 查询。就数据库性能而言,这可能是危险的,但更多的是关于 SoC。调用者不关心数据源;它只想要数据。

      【讨论】:

        猜你喜欢
        • 2021-12-12
        • 2011-03-03
        • 1970-01-01
        • 2010-10-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多