我的 Entity Framework 存储库和服务层方法应返回哪些类型:List、IEnumerable、IQueryable?

Posted

技术标签:

【中文标题】我的 Entity Framework 存储库和服务层方法应返回哪些类型:List、IEnumerable、IQueryable?【英文标题】:Which types should my Entity Framework repository and service layer methods return: List, IEnumerable, IQueryable? 【发布时间】:2011-09-18 23:29:48 【问题描述】:

我有一个具体的存储库实现,它返回实体的 IQueryable:

public class Repository

    private AppDbContext context;

    public Repository()
    
        context = new AppDbContext();
    


    public IQueryable<Post> GetPosts()
    
        return context.Posts;
    

然后,我的服务层可以根据其他方法(位置、分页等)的需要执行 LINQ

现在我的服务层设置为返回 IEnumerable:

public IEnumerable<Post> GetPageOfPosts(int pageNumber, int pageSize)
 
    Repository postRepo = new Repository();

    var posts = (from p in postRepo.GetPosts()  //this is IQueryable
                orderby p.PostDate descending
                select p)
                .Skip((pageNumber - 1) * pageSize)
                .Take(pageSize);

    return posts;

这意味着如果我想绑定到中继器或其他控件,我必须在我的代码隐藏中执行 ToList()。

这是处理返回类型的最佳方式,还是我需要在从服务层方法返回之前转换为列表?

【问题讨论】:

【参考方案1】:

您的返回类型应始终在继承层次结构中尽可能高(或者我应该将其写为,如果基础朝向底部) .如果你所有的方法都需要IQueryable&lt;T&gt;,那么所有的返回值都应该放弃那个类型。

也就是说,IEnumerable&lt;T&gt; 有一个方法 (AsQueryable()),您可以调用它来实现(我认为是)所需的结果。

【讨论】:

我只打算使用存储库中的 IQueryable,因此我可以根据需要从服务层进行过滤/排序/分页,而无需使用额外的传递方法复制代码。【参考方案2】:

这两种方法都是可能的,只是选择问题。

一旦你使用IQueryable,你就有了一个简单的存储库,它在大多数情况下都可以工作,但它的可测试性更差,因为在IQueryable 上定义的查询是 linq-to-entities。如果你模拟存储库,它们是单元测试中的 linq-to-objects = 你没有测试你的真实实现。您需要集成测试来测试您的查询逻辑。

一旦您使用IEnumerable,您将拥有非常复杂的存储库公共接口——您需要为每个需要在存储库中公开特殊查询的实体提供特殊的存储库类型。这种存储库在存储过程中更为常见——存储库中的每个方法都映射到单个存储过程。这种类型的存储库提供了更好的关注点分离和更少的抽象泄漏,但同时它消除了很多 ORM 和 Linq 的灵活性。

最后,您可以采用组合方法,其中方法为最常见的场景(更经常使用的查询)返回 IEnumerable,另一种方法为罕见或复杂的动态构建查询公开 IQueryable

编辑:

如 cmets 中所述,使用 IQueryable 有一些副作用。当您公开 IQueryable 时,您必须保持上下文活动,直到您执行查询 - IQueryable 以与 IEnumerable 相同的方式使用延迟执行,因此除非您调用 ToListFirst 或其他执行您的查询的函数仍然需要您的上下文。

实现这一点的最简单方法是在存储库中使用一次性模式 - 在其构造函数中创建上下文并在存储库释放时释放它。然后您可以使用using 块并在其中执行查询。这种方法适用于非常简单的场景,您对每个存储库的单个上下文感到满意。更复杂(和常见)的场景需要在多个存储库之间共享上下文。在这种情况下,您可以使用上下文提供者/工厂(一次性)之类的东西并将工厂注入存储库构造函数(或允许提供者创建存储库)。这导致了 DAL 层工厂和自定义工作单元。

【讨论】:

仍然学习和系统不是太复杂,所以尽量避免任何复杂的架构,但仍然分离关注点。那么,最好让服务层消费者(UI/Codebehind)根据需要执行 ToList? 我更喜欢它,但它包含一些测试复杂性(如果您使用自动化测试),并且需要仔细控制上下文的生命周期(正如@jwJung 指出的那样),因为当您调用 ToList 时,上下文必须还活着。 所以为了避免上下文的潜在生命周期,我应该在服务层中执行 ToList,以便我知道数据在到达表示层时已从数据库中提取?除了使用(上下文)之外,我还应该做什么?将 dbcontext 移动到服务层?在带有接口的完整通用实现和将所有代码按程序放入代码隐藏之间必须有一些中间立场?! 这通常是通过一些上下文工厂或自定义工作单元来完成的。在这种情况下,您不调用using(context),但您有一些中心位置可以创建和处理上下文/uow - 例如,您可以使用上下文工厂检查上下文是否在HttpContext.Current.Items 中,如果是,则返回此实例,如果不是它创建一个新的,将其存储在项目中并返回它。您还需要自定义 IHttpModule 在请求结束时处理上下文。【参考方案3】:

您的问题的另一个词似乎需要确定AppDbContext 的处置时间或位置。

如果你不dispose,也就是应用退出的时候就dispose,返回IEnumerable/IQueryable没问题,没有实际数据。但是,在处置 AppDbContext 之前,您需要将类型作为 IList 返回,并包含实际数据。

更新: 尽管您已经知道,但我认为您需要了解以下代码含义。

//outside of this code is refered to your code.

//Returning IEnumerable could be used outside this scope if AppDbContext is ensured no disposing
public IEnumerable<Post> GetIEnumerableWithoutActualData()

    return context.Posts;


//Even if AppDbContext is disposed, IEnumerable could be used.
public IEnumerable<Post> GetIEnumerableWithActualData()

    return context.Posts.ToList();

【讨论】:

@jwJung - 只要我在“使用”语句中使用上下文,我就不必显式处理它,对吗? 但是,如果您将类型返回为 IQueryable,没有实际数据,我认为该类型在处理 AppDbContext 后会抛出 System.ObjectDisposedException。因此,您需要在处置之前将类型存档为 IList。 +1 非常好。返回 IQueryable 后,您必须控制存储库外上下文的生命周期。 一旦我查询并转换为 IEnumerable,我应该是安全的,对吗? 在 repo 中使用 IQueryable 的全部原因是这样我就可以从服务层进行过滤和分页,而不必为每个 where/paging 需要在 repo 中创建额外的方法。也许两全其美的方法是从 repo 执行 IQueryable,然后确保我从我的服务层到 ToList(),这样我就不应该遇到 DbContext 生命周期问题。其他人是如何处理此类问题的?

以上是关于我的 Entity Framework 存储库和服务层方法应返回哪些类型:List、IEnumerable、IQueryable?的主要内容,如果未能解决你的问题,请参考以下文章

在Entity Framework 4中使用存储库/ uow模式的多个ObjectContexts

Entity Framework 4.1 DbContext API 中的接口和存储库抽象中断子查询?

Entity Framework 4.1 和 NHibernate 的获取策略封装

Entity Framework 4.1 - 映射错误的模式

为啥 Entity framework Entity Master Details Entity Edit

Entity Framework Code First Repository 真的很通用并且有两个上下文的问题