返回IEnumerable <T>与IQueryable <T>

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

这两个都是延期执行,什么时候应该比另一个更受欢迎?

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

不同的是, IQueryable<T>是允许LINQ到SQL(LINQ到任何事情)的接口。 所以如果你进一步优化你的查询在一个IQueryable<T> ,那么这个查询将在数据库中执行,如果可能的话。

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

在代码中:

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

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

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

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

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

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

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

您可能已经使用了这两种,但没有意识到这一点,因为两者都使用相同的语法来调用:

例如, Where(x => x.City == "<City>")IEnumerableIQueryable都起作用

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

  • IQueryable集合上使用Where()时,编译器将expression式树传递给Where() 。 expression式树就像reflection系统,但代码。 编译器将你的代码转换成一个数据结构,描述你的代码以一种容易理解的格式进行的操作。

为什么要打扰这个expression树的东西? 我只想要Where()来过滤我的数据。 主要原因是EF和Linq2SQL ORM都可以将expression式树直接转换成SQL,在那里你的代码执行得更快。

哦,这听起来像一个免费的性能提升,我应该在这种情况下使用AsQueryable()吗? 不, IQueryable只有在底层的数据提供者可以做些什么的时候才有用。 像常规List转换为IQueryable不会给你任何好处。

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

至于哪一个比另一个更受欢迎,这取决于你的底层数据源是什么。

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

返回一个IQueryable(顺便说一下实现了IEnumerable)提供了额外的function,可以将你的查询翻译成对底层源(LINQ to SQL,LINQ to XML等)更好的performance。

是的,两者都使用延期执行。 让我们来说明使用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万条logging的WebLog表,运行该代码块大约需要90秒。

所以,所有的表logging都是以对象的forms加载到内存中的,然后是每个.Where()它将成为内存中另一个针对这些对象的filter。

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

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

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

使用IQueryable运行这段代码大约需要4秒钟的时间。

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

一般来说,您要保留查询的原始静态types,直到它很重要。

由于这个原因,你可以定义你的variables为'var',而不是IQueryable<>IEnumerable<> ,你会知道你没有改变types。

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

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

一般来说,我会build议如下:

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

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

想象一下,IQueryable就是这样一个数据的“查询”(如果你愿意,可以对其进行优化)

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

有一篇关于如何误用IEnumerable<T>可以显着影响LINQ查询性能的简短源代码示例的博文: entity framework:IQueryable与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) })); } } 

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

以前已经有很多说过了,但是回到了根本上,以更技术性的方式:

  1. IEnumerable 是可以枚举的内存中的一个对象的集合 – 一个内存中的序列,可以迭代遍历(在foreach循环内部很容易,但只能使用IEnumerator )。 他们现在居住在记忆中。
  2. IQueryable 是一个expression式树 ,在某些时候可以被翻译成其他的东西, 并且能够枚举最终的结果 。 我想这是大多数人混淆。

他们显然有不同的内涵。

IQueryable代表一个expression式树(一个简单的查询),只要调用发布API,就像LINQ集合函数(Sum,Count等等)或ToList [Array,Dictionary, …]。 而且IQueryable对象也实现IEnumerableIEnumerable<T>所以如果它们代表一个查询那么这个查询的结果就可以被迭代。 这意味着IQueryable不必仅仅是查询。 正确的说法是他们是expression树

现在,如何执行这些expression式以及它们转向的是所谓的查询提供者(我们可以认为它们是expression式执行者)。

在entity framework的世界(这是神秘的基础数据源提供者,或查询提供者), IQueryableexpression式被翻译成本地T-SQL查询。 Nhibernate与他们做类似的事情。 例如,您可以按照LINQ:构buildIQueryable提供程序链接中所描述的概念编写自己的代码,并且您可能希望为您的产品商店提供程序服务定制查询API。

所以基本上, IQueryable对象一直在构build,直到我们明确地释放它们,并且告诉系统把它们重写成SQL或者其它东西,并且发送执行链以便继续处理。

就像要推迟执行一样,只要某些API针对序列(相同的Count,ToList等)被调用,它就是一个LINQfunction,用于在内存中保留expression式树scheme并将其发送到执行中。

两者的正确使用在很大程度上取决于你面对的特定情况下的任务。 对于众所周知的存储库模式,我个人select返回IList ,即通过列表(索引器等)的IEnumerable 。 所以这是我的build议,只能在代码库中的任何其他地方使用IQueryable和IEnumerable。 不是说关于IQueryable的可testing性问题,并且破坏了关注点分离原则。 如果您从存储库中返回expression式,则消费者可能会按照他们所希望的方式使用持久层。

)(从评论中的讨论))他们都不是内存中的对象,因为它们本身不是真正的types,它们是一种types的标记 – 如果你想要深入的话。 但它是有道理的(这就是为什么即使MSDN这样)把IEnumerables作为内存中的集合,而IQueryables作为expression式树。 重点在于IQueryable接口inheritanceIEnumerable接口,因此如果它表示查询,则可以枚举该查询的结果。 枚举会导致与IQueryable对象关联的expression式树被执行。 所以,实际上,如果没有内存中的对象,就不能真正调用任何IEnumerable成员。 如果你这样做的话,它会进去的,反正,如果不是空的话。 IQueryables只是查询,而不是数据。

我最近遇到了IEnumrable诉IQueryable的一个问题。 所使用的algorithm首先执行IQueryable查询以获得一组结果。 然后这些被传递给一个foreach循环,并将这些项目实例化为一个EF类。 然后,这个EF类在Linq to Entity查询的from子句中使用,导致结果为IEnumerable。 我对EF和Linq for Entities还是比较陌生的,所以花了一些时间才弄清楚了瓶颈是什么。 使用MiniProfiling,我find了查询,然后将所有的单个操作转换为单个的IQueryable Linq for Entities查询。 IEnumerable耗时15秒,IQueryable需要0.5秒才能执行。 有三个表涉及,并在阅读后,我相信IEnumerable查询实际上是形成一个三表跨产品和筛选结果。

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

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

(1) IQueryable扩展了IEnumerable接口。 (你可以发送一个IQueryable的东西,预计IEnumerable没有错误。)

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

换句话说, IEnumerables不是唯一的“内存”。 IQueryables并不总是在数据库上执行。 IEnumerable必须把东西加载到内存中(一旦被检索,可能是懒惰的),因为它没有抽象的数据提供者。 IQueryables依赖抽象提供者(如LINQ到SQL),尽pipe这也可能是.NET内存提供者。

示例用例

(a)从EF上下文中获取IQueryable的logging列表。 (没有logging在内存中)

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

(c)迭代并从视图访问数据集的logging,子实体和属性。 (可能会导致exception!)

可能的问题

(1) IEnumerable尝试延迟加载和您的数据上下文已过期。 抛出exception,因为提供者不再可用。

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

(3)多个活动结果集(MARS)。 如果您在foreach( var record in resultSet )集中的foreach( var record in resultSet )块中遍历IEnumerable并同时尝试访问record.childEntity.childProperty ,那么由于延迟加载数据集和关系实体,最终可能会导致MARS。 如果在连接string中未启用,将会导致exception。

  • 我发现在连接string中启用MARS是不可靠的。 我build议你避免MARS,除非它被很好的理解和明确的期望。

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

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

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

返回IQueryable <T>与IEnumerable <T>之间的区别

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

例如,我们可以考虑一个例子,其中我们的数据库中有一个用户的10000个logging,我们说只有900个是活动用户,所以在这种情况下,如果我们使用“IEnumerable”,那么它首先将所有10,000条logging加载到内存中,然后在其上应用IsActivefilter,最终返回900个活动用户。

而另一方面,如果我们使用“IQueryable”,则会直接在数据库上应用IsActivefilter,从那里直接返回900个活动用户。

参考链接

我们可以用同样的方法,只是在performance上有所不同。

IQueryable只能以有效的方式在数据库中执行。 这意味着它会创build一个完整的select查询并只获取相关的logging。

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

但是如果我们使用了IEnumerable,那么查询就像select * from Customer where name like 'Nimal%' ,前十名将会在C#编码级别被过滤掉(它从数据库获取所有的客户logging并传递给C#) 。

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

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

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

当执行查询时,IEnumerable加载所有的数据,如果我们需要过滤它,过滤本身就在客户端完成。

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

IQueryable对象提供对数据库的远程访问,并允许您以直接的顺序从头到尾或以相反的顺序导航数据。 在创build查询的过程中,返回的对象是IQueryable,对查询进行了优化。 因此,执行期间消耗的内存更less,networking带宽更less,但与同时返回IEnumerable对象的查询相比,它可以稍微缓慢地处理。

该选什么?

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

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

在使用LINQ to Entities时,了解何时使用IEnumerable和IQueryable是很重要的。 如果我们使用IEnumerable,查询将立即执行。 如果我们使用IQueryable,查询执行将被推迟到应用程序请求枚举。 现在让我们看看在决定是使用IQueryable还是IEnumerable的时候应该考虑什么。 使用IQueryable使您有机会使用多个语句创build复杂的LINQ查询,而无需在数据库级别执行查询。 查询只有在最终的LINQ查询被枚举时才会被执行。