IQueryable和IEnumerable,IList的区别

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了IQueryable和IEnumerable,IList的区别相关的知识,希望对你有一定的参考价值。

参考技术A 两者区别几点1、Collection要支持foreach式遍历必须实现IEnumerable接口(亦即必须某种式返IEnumerator object)

2、IEnumerator object具体实现iterator(通MoveNext()Reset()Current)

3、两接口用词选择看其同:IEnumerable声明式接口声明实现该接口class枚举(enumerable)并没说明何实现枚举器(iterator);IEnumerator实现式接口IEnumerator objectiterator

4、IEnumerableIEnumerator通IEnumerableGetEnumerator()建立连接client通IEnumerableGetEnumerator()IEnumerator object意义GetEnumerator()看作IEnumerator objectfactory method未尝
IEnumerator 所枚举数基接口

枚举数允许读取集合数据枚举数用于修改基础集合

初枚举数定位于集合第元素前面Reset 枚举数返位置位置调用 Current 引发异读取 Current 值前必须调用 MoveNext 枚举数提前集合第元素

调用 MoveNext 或 Reset 前Current 返同象MoveNext Current 设置元素

传递集合末尾枚举数放集合元素面且调用 MoveNext 返 false调用 MoveNext 返 false则调用 Current 引发异若要再 Current 设置集合第元素调用 Reset再调用 MoveNext

要集合保持变枚举数保持效集合进行更改(例添加、修改或删除元素)则该枚举数失效且恢复并且 MoveNext 或 Reset 调用引发 InvalidOperationException MoveNext Current 间修改集合即使枚举数已经效Current 返所设置元素

枚举数没集合独占访问权;枚举集合本质线程安全程甚至集合进行同步处理其线程仍修改该集合导致枚举数引发异若要枚举程保证线程安全整枚举程锁定集合或者捕捉由于其线程进行更改引发异

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

【中文标题】我的 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 生命周期问题。其他人是如何处理此类问题的?

以上是关于IQueryable和IEnumerable,IList的区别的主要内容,如果未能解决你的问题,请参考以下文章

IQueryable 和 IEnumerable 有啥区别 [重复]

IEnumerable 和 IQueryable

IQueryable 和 IEnumerable 之间差异的真实示例? [复制]

IQueryable和IEnumerable,IList的区别

C# IQueryable和IEnumerable的区别

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