提问



返回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;


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

最佳参考


是的,两者都会给你延期执行。[105]


不同之处在于IQueryable<T>是允许LINQ-to-SQL(LINQ.-to-anything真正)工作的接口。因此,如果您在IQueryable<T>上进一步细化查询,则该查询将在数据库中执行,如果可能的话。 [106] [107]


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


在代码中:


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>上执行此操作将导致所有行都加载到内存中。[109] [110] [111] [112] [113]

其它参考1


最好的答案是好的,但它没有提到解释两个接口如何的表达式树。基本上,有两组相同的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()。表达式树就像反射系统,但代码。编译器将您的代码转换为数据结构,以一种易于理解的格式描述您的代码所执行的操作。



为什么要打扰这个表达式树的东西?我只想Where()来过滤我的数据。
主要原因是EF和Linq2SQL ORM都可以将表达式树直接转换为SQL,您的代码执行速度会快得多。


哦,这听起来像一个免费的性能提升,我应该在那个地方使用AsQueryable()吗?
不,IQueryable仅在底层数据提供者可以使用它时才有用。转换为常规ListIQueryable之类的东西不会给你带来任何好处。

其它参考2


是的,都使用延期执行。让我们用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查询。

其它参考3


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


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


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


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

其它参考4


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


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


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


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

其它参考5


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


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


如果你想传输一组对象来枚举,只需要使用IEnumerable。



  想象一下IQueryable就像它是什么,数据的查询(你可以根据需要改进)

  
  IEnumerable是一组可以枚举的对象(已经接收或创建过)。


其它参考6


有一篇博客文章,内容简要介绍了IEnumerable<T>的滥用如何极大地影响LINQ查询性能:实体框架:IQueryable与IEnumerable。[114]


如果我们深入挖掘并深入研究来源,我们可以看到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源中指定。

其它参考7


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



  1. IEnumerable 是内存中可以枚举的对象的集合 - 内存序列,可以迭代(在foreach中使其变得容易)循环,虽然你可以只用IEnumerator。它们按原样驻留在内存中。

  2. IQueryable 是一个表达式树,它会在某些点上被翻译成其他东西,能够枚举最终结果。我想这就是让大多数人感到困惑的原因。



他们显然有不同的内涵。


IQueryable表示一个表达式树(简单地称为查询),一旦调用了释放API,就会被底层查询提供者转换为其他东西,如LINQ聚合函数(Sum,Count等)或ToList [[数组,字典,...]]。并且IQueryable对象也实现IEnumerableIEnumerable<T>,以便 如果它们代表查询 ,则可以迭代该查询的结果。这意味着IQueryable不必仅仅是查询。正确的术语是表达树


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


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


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


好像要 延迟 执行它sa LINQ功能来保存内存中的表达式树方案并仅在需要时将其发送到执行中针对序列调用API(相同的Count,ToList等)。


两者的正确使用在很大程度上取决于您针对特定案例所面临的任务。对于众所周知的存储库模式,我个人选择返回IList,即IEnumerable超过列表(索引器和因此,我的建议是仅在存储库中使用IQueryable并在代码中的任何其他地方使用IEnumerable。不要说IQueryable分解并破坏关注点分离原则的可测试性问题。如果你从存储库中返回一个表达式,消费者可以按照自己的意愿使用持久层。[118]


一点点添加到混乱:)(来自评论中的讨论))
它们都不是内存中的对象,因为它们本身并不是真正的类型,它们是一种类型的标记 - 如果你想要那么深。但它有意义(这就是为什么甚至MSDN都这么说)将IEnumerables视为内存中的集合,而将IQueryables视为表达式树。重点是IQueryable接口继承IEnumerable接口,以便它代表一个查询,可以枚举该查询的结果。枚举导致与IQueryable对象关联的表达式树被执行。
所以,事实上,你不能在没有内存中的对象的情况下调用任何IEnumerable成员。如果你这样做,它会进入那里,无论如何,如果它不是空的。 IQueryables只是查询,而不是数据。[119]

其它参考8


我最近遇到了IEnumrable诉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作为经验法则,并对您的工作进行分析,以使您的更改可衡量。

其它参考9


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


(1)IQueryable扩展IEnumerable接口。 (您可以将IQueryable发送到期望IEnumerable而没有错误的内容。)


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


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


示例用例


(a)从EF背景中检索IQueryable记录列表。 (没有记录在内存中。)


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


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


可能出现的问题


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


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


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


解决方案



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



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


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

其它参考10


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


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


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


参考链接[120]

其它参考11


这些是IQueryable<T>IEnumerable<T>之间的一些差异


[121]

其它参考12


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


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


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


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

其它参考13


除了前2个非常好的答案(由driis& by Jacob):



  IEnumerable的
  interface位于System.Collections命名空间中。



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


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



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



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


选择什么?


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


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

其它参考14


在使用LINQ to Entities时,重要的是要了解何时
使用IEnumerable和IQueryable。如果我们使用IEnumerable,
查询将立即执行。如果我们使用IQueryable,那么
查询执行将推迟到应用程序请求
枚举。
现在让我们看看在决定是否应该考虑时应该考虑什么
使用IQueryable或IEnumerable。使用IQueryable可以为您提供帮助
有机会使用多个语句创建复杂的LINQ查询
不在数据库级别执行查询。查询得到
仅在枚举最终LINQ查询时执行。