代码之家  ›  专栏  ›  技术社区  ›  stackoverflowuser

返回IEnumerable<t>与iqueryable<t>

  •  974
  • stackoverflowuser  · 技术社区  · 15 年前

    回来有什么区别 IQueryable<T> VS 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 回复  |  直到 15 年前
        1
  •  1625
  •   Vadim Ovchinnikov    8 年前

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

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

    对于 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);
    

    这是一个非常重要的区别,正在努力 可查询 在许多情况下,可以避免从数据库返回过多的行。另一个主要示例是进行分页:如果使用 Take Skip IQueryable ,您将只获取请求的行数;在 IEnumerable<t> 将导致所有行加载到内存中。

        2
  •  244
  •   Vadim Ovchinnikov    8 年前

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

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

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

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

    例如 Where(x => x.City == "<City>") 双方作品 可枚举的 可查询的

    • 使用时 其中() 关于一个 可枚举的 集合,编译器将已编译函数传递给 其中()

    • 使用时 其中() 关于一个 可查询的 集合,编译器将表达式树传递给 其中() . 表达式树类似于反射系统,但用于代码。编译器将您的代码转换成一个数据结构,该结构以易于理解的格式描述代码的功能。

    为什么要麻烦这个表达树的事情?我只是想要 其中() 过滤我的数据。 主要原因是,EF和Linq2SQL窗体都可以将表达式树直接转换为SQL,在SQL中,代码执行速度更快。

    哦,这听起来像是免费的性能提升,我应该使用吗 AsQueryable() 在那种情况下到处都是? 不, 可查询的 仅当基础数据提供程序可以对其执行某些操作时才有用。转换类似正则的内容 List 可查询的 不会给你任何好处。

        3
  •  68
  •   Peter Mortensen Pieter Jan Bonestroo    9 年前

    是的,两者都使用延迟执行。让我们用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 Profiler中,我们发现一个命令等于:

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

    对一个有100万条记录的weblog表运行该代码块大约需要90秒。

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

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

    在SQL Server Profiler中,我们发现一个命令等于:

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

    运行此代码块大约需要4秒钟,使用 可查询的 .

    IQueryable有一个名为 Expression 它存储一个树表达式,当我们使用 result 在我们的示例中(称为延迟执行),在最后,此表达式将转换为要在数据库引擎上运行的SQL查询。

        4
  •  54
  •   d219    7 年前

    两个都会让你延期执行,是的。

    至于哪个比另一个更受欢迎,这取决于基础数据源是什么。

    返回一个 IEnumerable 将自动强制运行时使用Linq to对象查询集合。

    返回一个 IQueryable (实现 可枚举的 顺便说一句)提供了额外的功能,可以将查询转换为在底层源代码(linq to sql、linq to xml等)上可能更好地执行的功能。

        5
  •  26
  •   E. Moffat    7 年前

    一般而言,我建议如下:

    • 返回 IQueryable<T> 如果您想让开发人员使用您的方法来优化您在执行前返回的查询。

    • 返回 IEnumerable 如果要传输一组要枚举的对象。

    想象一个 IQueryable 这就是它的本质——数据的“查询”(如果你想的话,可以对其进行优化)。安 可枚举的 是一组可以枚举的对象(已接收或已创建)。

        6
  •  23
  •   Stewbob    13 年前

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

    因此,您可以将变量定义为“var”,而不是 IQueryable<> IEnumerable<> 你会知道你没有改变类型。

    如果你从一开始 可查询<> ,您通常希望将其作为 可查询<> 直到有令人信服的理由改变它。这样做的原因是,您希望向查询处理器提供尽可能多的信息。例如,如果只使用10个结果 Take(10) )然后您希望SQL Server知道这一点,这样它就可以优化其查询计划并只向您发送将要使用的数据。

    将类型从 可查询<> IEnumerable<gt; 可能是您正在调用某个扩展函数, 可查询<> 在特定对象中,要么无法处理,要么处理效率低下。在这种情况下,您可能希望将类型转换为 IEnumerable<gt; (通过分配给类型的变量 IEnumerable<gt; 或通过使用 AsEnumerable 例如,扩展方法),以便您调用的扩展函数最终成为 Enumerable 类而不是 Queryable 班级。

        7
  •  20
  •   Peter Mortensen Pieter Jan Bonestroo    9 年前

    前面已经说过很多,但要回到根源上来,用一种更技术的方式:

    1. IEnumerable 是内存中可以枚举的对象集合 -内存中的一个序列,它使迭代成为可能 foreach 循环,尽管你可以 IEnumerator 只)。它们仍然存在于记忆中。
    2. IQueryable 是表达式树 在某种程度上会被翻译成别的东西 能够列举最终结果 . 我想这就是让大多数人困惑的原因。

    它们显然有不同的含义。

    可查询的 表示一个表达式树(简单地说是一个查询),一旦调用发布API(如LINQ聚合函数(SUM、COUNT等)或TOLIST[数组、字典等),底层查询提供程序就会将其转换为其他内容。和 可查询的 对象也实现 可枚举的 , IEnumerable<T> 以便 如果它们代表一个查询 可以迭代该查询的结果。这意味着iQueryable不必只是查询。正确的说法是 表达式树 .

    现在,这些表达式是如何执行的,以及它们变成什么,都取决于所谓的查询提供者(我们可以认为是表达式执行者)。

    Entity Framework 世界(即神秘的基础数据源提供程序或查询提供程序) 可查询的 表达式转换为本机表达式 T-SQL 查询。 Nhibernate 和他们做类似的事情。您可以根据中描述的概念编写自己的一个 LINQ: Building an IQueryable Provider 例如,链接,您可能希望为产品存储提供程序服务提供自定义查询API。

    基本上, 可查询的 对象一直在被构造,直到我们显式地释放它们,并告诉系统将它们重写为SQL或其他类型,然后向下发送执行链进行后续处理。

    仿佛 推迟 执行这是一个 LINQ 当针对序列调用某些API时(相同的计数、tolist等),功能将表达式树方案保留在内存中,并仅按需将其发送到执行中。

    两者的正确使用在很大程度上取决于您在特定情况下所面临的任务。对于众所周知的存储库模式,我个人选择返回 IList ,那就是 可枚举的 超过列表(索引器等)。所以这是我的建议 可查询的 仅在存储库和代码中其他任何位置的IEnumerable中。不说可测试性关系到 可查询的 破坏和破坏 separation of concerns 原理。如果您从存储库中返回一个表达式,那么用户可能会按照自己的意愿使用持久层。

    有点混乱:(来自评论中的讨论) 它们中没有一个是内存中的对象,因为它们本身不是真正的类型,它们是类型的标记——如果你想深入了解的话。但这是有道理的(这就是为什么 MSDN 这样说)将IEnumerable视为内存集合,而iqueryables则视为表达式树。重点是iQuery接口继承了IEnumerable接口,因此如果它表示一个查询,则可以枚举该查询的结果。枚举导致执行与IQueryable对象关联的表达式树。 因此,实际上,如果没有内存中的对象,就不能真正调用任何IEnumerable成员。如果你这样做,不管怎样,如果它不是空的,它会进入那里。iqueryables只是查询,而不是数据。

        8
  •  17
  •   Olexander Ivanitskyi    12 年前

    有一篇博文简要介绍了如何滥用 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 来源。

        9
  •  10
  •   d219    7 年前

    我最近遇到一个问题 IEnumerable v.诉 IQueryable . 首先使用的算法执行了 可查询的 查询以获取一组结果。然后这些被传给 foreach 循环,将项实例化为实体框架(EF)类。这个ef类被用于 from linq to entity查询的子句,导致结果为 可枚举的 .

    我对实体的ef和linq还比较陌生,所以花了一段时间才弄清楚瓶颈是什么。使用微型分析,我找到了查询,然后将所有单个操作转换为单个操作 可查询的 用于实体查询的Linq。这个 可枚举的 花了15秒 可查询的 执行耗时0.5秒。一共有三张桌子,我读过之后,相信 可枚举的 查询实际上是形成一个三表交叉积并过滤结果。

    试着用iqueryables作为经验法则,对你的工作进行分析,使你的变化是可测量的。

        10
  •  9
  •   Alexander Pritchard    10 年前

    我想澄清一些事情,由于似乎冲突的反应(主要围绕IEnumerable)。

    (1) IQueryable 扩展了 IEnumerable 接口。(你可以发送一个 可查询的 期望得到的东西 可枚举的 没有错误。)

    (2)二者兼而有之 可查询的 可枚举的 在对结果集进行迭代时,Linq尝试延迟加载。(请注意,可以在每种类型的接口扩展方法中看到实现。)

    换言之, IEnumerables 不是完全“在记忆中”。 IQueryables 并不总是在数据库上执行。 可枚举的 必须将内容加载到内存中(一旦检索到,可能是延迟的),因为它没有抽象数据提供程序。 iQueReabes 依赖抽象提供程序(如Linq to SQL),尽管它也可以是.NET内存中的提供程序。

    样品使用案例

    (a)检索记录列表 可查询的 来自EF上下文。(内存中没有记录。)

    (b)通过 可查询的 到其模型为 可枚举的 . (有效的。 可查询的 延伸 可枚举的 )

    (C)迭代并从视图访问数据集的记录、子实体和属性。(可能导致例外!)

    可能的问题

    (1) 可枚举的 尝试延迟加载,数据上下文已过期。由于提供程序不再可用,引发异常。

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

    (3)多个活动结果集(mars)。如果您正在迭代 可枚举的 在一个 foreach( var record in resultSet ) 阻止并同时尝试访问 record.childEntity.childProperty 因为数据集和关系实体的延迟加载,您可能会以mars结束。如果未在连接字符串中启用,这将导致异常。

    解决方案

    • 我发现在连接字符串中启用mars不可靠。我建议你避开火星,除非它被很好的理解和明确的要求。

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

    在您访问相关实体的情况下,您可能仍然需要数据上下文。或者您可以禁用实体代理并显式地 Include 您的相关实体 DbSet .

        11
  •  9
  •   Basheer AL-MOMANI    9 年前

    以下是 iQueryable<t> IEnumerable<t> 之间的一些区别

        12
  •  8
  •   Tabish Usman Nudier Mena    9 年前

    __IEnumerable_157;和_156;iqueryable_157;之间的主要区别在于执行过滤器逻辑的位置。一个在客户端(内存中)执行,另一个在数据库上执行。

    例如,我们可以考虑这样一个例子:在我们的数据库中,一个用户有10000条记录,假设只有900条记录是活动用户,所以在本例中,如果我们使用IEnumerable,那么首先它将在内存中加载所有10000条记录,然后对其应用isactive过滤器,最后返回900个活动用户。

    另一方面,同样的情况下,如果我们使用iqueryable,它将直接在数据库上应用isactive过滤器,直接从数据库返回900个活动用户。

    参考文献 Link

        13
  •  4
  •   Peter Mortensen Pieter Jan Bonestroo    9 年前

    我们可以以相同的方式使用这两种方法,而且它们在性能上只是不同的。

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

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

    但是如果我们使用IEnumerable,那么查询应该是 select * from Customer where name like ‘Nimal%’ 前十个将在C编码级别进行筛选(它从数据库中获取所有客户记录并将其传递到C)。

        14
  •  4
  •   Gleb B    8 年前

    除了前2个非常好的答案(由Dris和Jacob提供):

    可枚举的 接口在System.Collections命名空间中。

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

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

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

    IQueryable对象提供对数据库的远程访问,并允许您以从开始到结束的直接顺序或相反的顺序浏览数据。在创建查询的过程中,返回的对象是IQueryable,对查询进行了优化。因此,在执行过程中消耗的内存更少,网络带宽更少,但同时,它的处理速度比返回IEnumerable对象的查询稍慢。

    选择什么?

    如果您需要整个返回的数据集,那么最好使用IEnumerable,它提供了最大的速度。

    如果您不需要整个返回数据集,而只需要一些过滤后的数据,那么最好使用iqueryable。

        15
  •  -2
  •   Arpit Srivastava    7 年前

    IEnumrable将数据存储到内存中

    但如果是iqueuable,它不会存储在内存中。

    有关详细信息,请使用SQL事件探查器进行检查

    第一次击中你 使用iQueryable查询 并查看执行的查询

    然后尝试从IEnumable