返回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
上使用Take
和Skip
,你将只获得请求的行数; 在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>")
对IEnumerable
和IQueryable
都起作用
-
在
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
源中指定。
以前已经有很多说过了,但是回到了根本上,以更技术性的方式:
-
IEnumerable
是可以枚举的内存中的一个对象的集合 – 一个内存中的序列,可以迭代遍历(在foreach
循环内部很容易,但只能使用IEnumerator
)。 他们现在居住在记忆中。 -
IQueryable
是一个expression式树 ,在某些时候可以被翻译成其他的东西, 并且能够枚举最终的结果 。 我想这是大多数人混淆。
他们显然有不同的内涵。
IQueryable
代表一个expression式树(一个简单的查询),只要调用发布API,就像LINQ集合函数(Sum,Count等等)或ToList [Array,Dictionary, …]。 而且IQueryable
对象也实现IEnumerable
, IEnumerable<T>
所以如果它们代表一个查询 , 那么这个查询的结果就可以被迭代。 这意味着IQueryable不必仅仅是查询。 正确的说法是他们是expression树 。
现在,如何执行这些expression式以及它们转向的是所谓的查询提供者(我们可以认为它们是expression式执行者)。
在entity framework的世界(这是神秘的基础数据源提供者,或查询提供者), IQueryable
expression式被翻译成本地T-SQL查询。 Nhibernate
与他们做类似的事情。 例如,您可以按照LINQ:构buildIQueryable提供程序链接中所描述的概念编写自己的代码,并且您可能希望为您的产品商店提供程序服务定制查询API。
所以基本上, IQueryable
对象一直在构build,直到我们明确地释放它们,并且告诉系统把它们重写成SQL或者其它东西,并且发送执行链以便继续处理。
就像要推迟执行一样,只要某些API针对序列(相同的Count,ToList等)被调用,它就是一个LINQ
function,用于在内存中保留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)在迭代结果集时, IQueryable
和IEnumerable
尝试延迟加载。 (请注意,可以在每种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>
之间的一些区别
“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查询被枚举时才会被执行。