首页 文章

返回IEnumerable <T>与IQueryable <T>

提问于
浏览
953

返回 IQueryable<T>IEnumerable<T> 有什么区别?

IQueryable<Customer> custs = from c in db.Customers
where c.City == "<City>"
select c;

IEnumerable<Customer> custs = from c in db.Customers
where c.City == "<City>"
select c;

两者都会延迟执行吗?什么时候应该优先于另一个?

15 回答

  • 23

    一般而言,我会建议如下:

    • 如果要使用您的方法启用开发人员来优化您在执行之前返回的查询,请返回 IQueryable<T> .

    • 如果要传输一组对象以进行枚举,则返回 IEnumerable .

    想象一下 IQueryable 就是这样 - 数据的"query"(你可以根据需要进行改进) . IEnumerable 是一组可以枚举的对象(已经接收或创建过) .

  • 1582

    之前已经说过很多,但是以更技术性的方式回到根源:

    • IEnumerable is a collection of objects in memory that you can enumerate - 一个内存中的序列,可以迭代(在 foreach 循环中很容易,但你只能使用 IEnumerator ) . 它们按原样驻留在内存中 .

    • IQueryable is an expression tree 将在某些时候被翻译成其他东西 with ability to enumerate over the final outcome . 我猜这是让大多数人感到困惑的原因 .

    他们显然有不同的内涵 .

    IQueryable 表示一个表达式树(简单地称为查询),一旦调用发布API,它将被底层查询提供者转换为其他内容树,如LINQ聚合函数(Sum,Count等)或ToList [Array,Dictionary, ...] . 并且 IQueryable 对象也实现 IEnumerableIEnumerable<T> ,以便 if they represent a query 可以迭代该查询的结果 . 这意味着IQueryable不必仅是查询 . 正确的术语是 expression trees .

    现在,如何执行这些表达式以及它们转向什么都取决于所谓的查询提供程序(表达式执行程序,我们可以认为它们) .

    Entity Framework世界(这是神秘的底层数据源提供程序或查询提供程序) IQueryable 表达式被转换为本机T-SQL查询 . Nhibernate 与他们做类似的事情 . 例如,您可以按照LINQ: Building an IQueryable Provider链接中描述的概念编写自己的概念,并且您可能希望为产品商店提供商服务提供自定义查询API .

    所以基本上, IQueryable 对象的构造一直很长,直到我们明确地释放它们并告诉系统将它们重写为SQL或其他任何东西并向下发送执行链以进行后续处理 .

    好像要 deferred 执行它是一个 LINQ 功能来保持内存中的表达式树方案,并且只在需要时,只要按顺序调用某些API(相同的Count,ToList等),就将它发送到执行中 .

    两者的正确使用在很大程度上取决于您针对特定案例所面临的任务 . 对于众所周知的存储库模式,我个人选择返回 IList ,即列表(索引器等) IEnumerable . 因此,我建议仅在存储库中使用 IQueryable ,在代码中的任何其他地方使用IEnumerable . 没有说关于 IQueryable 破坏的可测性问题并且破坏了separation of concerns原则 . 如果从存储库中返回表达式,则消费者可以按照自己的意愿使用持久层 .

    混乱的一点点:)(来自评论中的讨论))它们都不是内存中的对象,因为它们是一种类型的标记 - 如果你想深入了解它 . 但它有意义(这就是为什么甚至MSDN这样说)将IEnumerables视为内存中的集合,而将IQueryables视为表达式树 . 关键是IQueryable接口继承IEnumerable接口,因此如果它表示查询,则可以枚举该查询的结果 . 枚举导致与IQueryable对象关联的表达式树被执行 . 所以,事实上,你可以't really call any IEnumerable member without having the object in the memory. It will get in there if you do, anyways, if it'不是空的 . IQueryables只是查询,而不是数据 .

  • 226

    最好的答案是好的,但它没有提到解释"how"两个接口不同的表达式树 . 基本上,有两组相同的LINQ扩展 . Where()Sum()Count()FirstOrDefault() 等都有两个版本:一个接受函数,另一个接受表达式 .

    • IEnumerable 版本签名是: Where(Func<Customer, bool> predicate)

    • IQueryable 版本签名是: Where(Expression<Func<Customer, bool>> predicate)

    您可能一直在使用这两者而没有意识到它,因为两者都使用相同的语法调用:

    例如 Where(x => x.City == "<City>") 适用于 IEnumerableIQueryable

    • IEnumerable 集合上使用 Where() 时,编译器将已编译的函数传递给 Where()

    • IQueryable 集合上使用 Where() 时,编译器将表达式树传递给 Where() . 表达式树就像反射系统,但代码 . 编译器将您的代码转换为数据结构,该数据结构以易于理解的格式描述您的代码所执行的操作 .

    Why bother with this expression tree thing? I just want Where() to filter my data. 主要原因是EF和Linq2SQL ORM可以将表达式树直接转换为SQL,您的代码执行速度会快得多 .

    Oh, that sounds like a free performance boost, should I use AsQueryable() all over the place in that case? 不, IQueryable 仅在基础数据提供者可以对其执行某些操作时才有用 . 将常规 List 转换为 IQueryable 并不会给您带来任何好处 .

  • 9

    我想澄清一些事情,因为看似相互矛盾的反应(主要围绕IEnumerable) .

    (1) IQueryable 扩展了 IEnumerable 接口 . (你可以发送一个 IQueryable 到期望 IEnumerable 没有错误的东西 . )

    (2)当迭代结果集时, IQueryableIEnumerable LINQ都尝试延迟加载 . (请注意,可以在每种类型的接口扩展方法中看到实现 . )

    换句话说, IEnumerables 不仅仅是"in-memory" . IQueryables 并不总是在数据库上执行 . IEnumerable 必须将内容加载到内存中(一旦检索,可能是懒惰),因为它没有抽象数据提供程序 . IQueryables 依赖于抽象提供程序(如LINQ-to-SQL),尽管这也可能是.NET内存提供程序 .

    Sample use case

    (a)从EF上下文中检索记录列表为 IQueryable . (没有记录在内存中 . )

    (b)将 IQueryable 传递给模型为 IEnumerable 的视图 . (有效. IQueryable 延伸 IEnumerable . )

    (c)从视图中迭代并访问数据集的记录,子实体和属性 . (可能会导致异常!)

    Possible Issues

    (1) IEnumerable 尝试延迟加载并且您的数据上下文已过期 . 抛出异常,因为提供程序不再可用 .

    (2)启用实体框架实体代理(默认),并尝试访问具有过期数据上下文的相关(虚拟)对象 . 与(1)相同 .

    (3)多个活动结果集(MARS) . 如果您在 foreach( var record in resultSet ) 块中迭代 IEnumerable 并同时尝试访问 record.childEntity.childProperty ,则由于数据集和关系实体的延迟加载,您可能最终得到MARS . 如果未在连接字符串中启用,则会导致异常 .

    Solution

    • 我发现在连接字符串中启用MARS的工作不可靠 . 我建议你避免使用MARS,除非它被很好地理解和明确需要 .

    通过调用 resultList = resultSet.ToList() 执行查询并存储结果这似乎是确保实体在内存中最直接的方法 .

    如果您正在访问相关实体,您可能仍需要数据上下文 . 或者,或者您可以从 DbSet 禁用实体代理和明确 Include 相关实体 .

  • 17

    在使用LINQ to Entities时,了解何时使用IEnumerable和IQueryable非常重要 . 如果我们使用IEnumerable,查询将立即执行 . 如果我们使用IQueryable,查询执行将推迟到应用程序请求枚举 . 现在让我们看看在决定是否使用IQueryable或IEnumerable时应该考虑什么 . 使用IQueryable可以使用多个语句创建复杂的LINQ查询,而无需在数据库级别执行查询 . 仅在枚举最终LINQ查询时才执行查询 .

  • 24

    “IEnumerable”和“IQueryable”之间的主要区别在于过滤器逻辑的执行位置 . 一个在客户端执行(在内存中),另一个在数据库上执行 .

    例如,我们可以考虑一个例子,我们在数据库中有一个用户的10,000条记录,让我们说只有900条是活跃用户,所以在这种情况下,如果我们使用“IEnumerable”,那么首先它会在内存中加载所有10,000条记录,然后在其上应用IsActive过滤器,最终返回900个活动用户 .

    另一方面,如果我们使用“IQueryable”,它将直接在数据库上应用IsActive过滤器,而直接从那里返回900个活动用户 .

    参考Link

  • 7

    除了前两个非常好的答案(由driis和雅各布):

    IEnumerable接口位于System.Collections命名空间中 .

    IEnumerable对象表示内存中的一组数据,只能向前移动此数据 . IEnumerable对象表示的查询是立即完全执行的,因此应用程序可以快速接收数据 .

    执行查询时,IEnumerable加载所有数据,如果需要对其进行过滤,则过滤本身在客户端完成 .

    IQueryable接口位于System.Linq命名空间中 .

    IQueryable对象提供对数据库的远程访问,允许您以直接顺序从头到尾或以相反的顺序浏览数据 . 在创建查询的过程中,返回的对象是IQueryable,查询已优化 . 因此,在执行期间消耗的内存更少,网络带宽更少,但同时它可以比返回IEnumerable对象的查询稍微慢一些地处理 .

    选择什么?

    如果您需要整套返回的数据,那就是更好地使用IEnumerable,它提供了最大的速度 .

    如果您不需要整套返回数据,但只需要一些过滤数据,那么最好使用IQueryable .

  • 4

    是的,都使用延期执行 . 让我们用SQL Server分析器来说明差异....

    当我们运行以下代码时:

    MarketDevEntities db = new MarketDevEntities();
    
    IEnumerable<WebLog> first = db.WebLogs;
    var second = first.Where(c => c.DurationSeconds > 10);
    var third = second.Where(c => c.WebLogID > 100);
    var result = third.Where(c => c.EmailAddress.Length > 11);
    
    Console.Write(result.First().UserName);
    

    在SQL Server探查器中,我们找到一个等于的命令:

    "SELECT * FROM [dbo].[WebLog]"
    

    对具有100万条记录的WebLog表运行该代码块大约需要90秒 .

    因此,所有表记录都作为对象加载到内存中,然后与每个.Where()一起,它将是内存中针对这些对象的另一个过滤器 .

    当我们在上面的例子(第二行)中使用 IQueryable 而不是 IEnumerable 时:

    在SQL Server探查器中,我们找到一个等于的命令:

    "SELECT TOP 1 * FROM [dbo].[WebLog] WHERE [DurationSeconds] > 10 AND [WebLogID] > 100 AND LEN([EmailAddress]) > 11"
    

    使用 IQueryable 运行此代码块大约需要四秒钟 .

    IQueryable有一个名为 Expression 的属性,它存储一个树表达式,它在我们的示例中使用 result 时开始创建(称为延迟执行),最后这个表达式将转换为SQL查询以在数据库引擎上运行 .

  • 7

    这些是 IQueryable<T>IEnumerable<T> 之间的一些区别

    difference between returning IQueryable vs. IEnumerable

  • 54

    我们可以以相同的方式使用它们,并且它们仅在性能上有所不同 .

    IQueryable只能以有效的方式对数据库执行 . 这意味着它创建了一个完整的选择查询,只获取相关记录 .

    例如,我们想要使用名称以“Nimal”开头的 top 10 客户 . 在这种情况下,选择查询将生成为 select top 10 * from Customer where name like ‘Nimal%’ .

    但是如果我们使用IEnumerable,那么查询就像 select * from Customer where name like ‘Nimal%’ ,前十名将在C#编码级别进行过滤(它从数据库获取所有客户记录并将它们传递给C#) .

  • 10

    是的,两者都会给你deferred execution .

    区别在于IQueryable<T>是允许LINQ-to-SQL(LINQ.-to-anything真正)工作的接口 . 因此,如果您在IQueryable<T>上进一步优化查询,则该查询将在数据库中执行(如果可能) .

    对于IEnumerable<T>情况,它将是LINQ-to-object,这意味着必须将与原始查询匹配的所有对象从数据库加载到内存中 .

    在代码中:

    IQueryable<Customer> custs = ...;
    // Later on...
    var goldCustomers = custs.Where(c => c.IsGold);
    

    该代码将执行SQL以仅选择黄金客户 . 另一方面,以下代码将在数据库中执行原始查询,然后过滤掉内存中的非黄金客户:

    IEnumerable<Customer> custs = ...;
    // Later on...
    var goldCustomers = custs.Where(c => c.IsGold);
    

    这是一个非常重要的区别,在IQueryable<T>上工作可以在很多情况下避免从数据库中返回太多行 . 另一个主要的例子是进行分页:如果你在IQueryable上使用TakeSkip,你将只获得所请求的行数;在IEnumerable<T>上执行此操作将导致所有行都加载到内存中 .

  • -4

    通常,您希望保留查询的原始静态类型,直到重要为止 .

    因此,您可以将变量定义为'var'而不是 IQueryable<>IEnumerable<> ,您将知道您没有更改类型 .

    如果您从 IQueryable<> 开始,通常希望将其保留为 IQueryable<> ,直到有一些令人信服的理由进行更改 . 原因是您希望为查询处理器提供尽可能多的信息 . 例如,如果您're only going to use 10 results (you'已调用 Take(10) ),那么您希望SQL Server了解它,以便它可以优化其查询计划并仅向您发送您将使用的数据 .

    将类型从 IQueryable<> 更改为 IEnumerable<> 的一个令人信服的理由可能是您正在调用某个扩展函数,即您的特定对象中的 IQueryable<> 的实现无法处理或处理效率低下 . 在这种情况下,您可能希望将类型转换为 IEnumerable<> (通过分配 IEnumerable<> 类型的变量或使用 AsEnumerable 扩展方法),以便您调用的扩展函数最终成为 Enumerable 类中的扩展函数而不是 Queryable 班 .

  • 62

    两者都会给你延期执行,是的 .

    至于哪个优先于另一个,它取决于您的基础数据源是什么 .

    返回 IEnumerable 将自动强制运行时使用LINQ to Objects来查询集合 .

    返回 IQueryable (顺便实现 IEnumerable )提供了额外的功能,可以将查询转换为可能在底层源(LINQ to SQL,LINQ to XML等)上执行得更好的内容 .

  • 19

    有一篇博客文章,内容简要介绍了 IEnumerable<T> 的滥用如何极大地影响LINQ查询性能:Entity Framework: IQueryable vs. IEnumerable .

    如果我们深入挖掘并查看源代码,我们可以看到 IEnumerable<T> 显然有不同的扩展方法:

    // Type: System.Linq.Enumerable
    // Assembly: System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
    // Assembly location: C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Core.dll
    public static class Enumerable
    {
        public static IEnumerable<TSource> Where<TSource>(
            this IEnumerable<TSource> source, 
            Func<TSource, bool> predicate)
        {
            return (IEnumerable<TSource>) 
                new Enumerable.WhereEnumerableIterator<TSource>(source, predicate);
        }
    }
    

    IQueryable<T>

    // Type: System.Linq.Queryable
    // Assembly: System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
    // Assembly location: C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Core.dll
    public static class Queryable
    {
        public static IQueryable<TSource> Where<TSource>(
            this IQueryable<TSource> source, 
            Expression<Func<TSource, bool>> predicate)
        {
            return source.Provider.CreateQuery<TSource>(
                Expression.Call(
                    null, 
                    ((MethodInfo) MethodBase.GetCurrentMethod()).MakeGenericMethod(
                        new Type[] { typeof(TSource) }), 
                        new Expression[] 
                            { source.Expression, Expression.Quote(predicate) }));
        }
    }
    

    第一个返回可枚举迭代器,第二个通过查询提供程序创建查询,在 IQueryable 中指定资源 .

  • 4

    我最近遇到了 IEnumerable v . IQueryable 的问题 . 首先使用的算法执行 IQueryable 查询以获得一组结果 . 然后将这些项传递给 foreach 循环,将项实例化为实体框架(EF)类 . 然后在Linq to Entity查询的 from 子句中使用此EF类,导致结果为 IEnumerable .

    我是EF和Linq for Entities的新手,因此需要一段时间来弄清楚瓶颈是什么 . 使用MiniProfiling,我找到了查询,然后将所有单个操作转换为单个 IQueryable Linq for Entities查询 . IEnumerable 耗时15秒, IQueryable 耗时0.5秒执行 . 涉及到三个表,在阅读完之后,我认为 IEnumerable 查询实际上形成了一个三表交叉产品并过滤结果 .

    尝试使用IQueryables作为经验法则,并对您的工作进行分析,以使您的更改可衡量 .

相关问题