返回的IQueryable与返回的IEnumerable有什么区别?
1 2 3 4 5 6 7
| 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是一个接口,它允许linq to sql(linq.-to anythy)工作。因此,如果您进一步改进了对IQueryable的查询,那么如果可能,该查询将在数据库中执行。
对于IEnumerable情况,它将是linq to object,这意味着所有与原始查询匹配的对象都必须从数据库加载到内存中。
在代码中:
1 2 3
| IQueryable<Customer> custs = ...;
// Later on...
var goldCustomers = custs.Where(c => c.IsGold); |
该代码将执行SQL以仅选择黄金客户。另一方面,以下代码将执行数据库中的原始查询,然后过滤掉内存中的非黄金客户:
1 2 3
| IEnumerable<Customer> custs = ...;
// Later on...
var goldCustomers = custs.Where(c => c.IsGold); |
这是一个非常重要的区别,在许多情况下,使用IQueryable可以避免从数据库返回过多的行。另一个主要的例子是进行分页:如果在IQueryable上使用Take和Skip,则只会得到请求的行数;在IEnumerable上进行分页将导致所有行都加载到内存中。
- 我最近遇到了这个问题,我花了一段时间才弄清楚原因。事实证明,我正在使用IEnumerable平均值构建部分查询,检索到的记录数为500k(慢!!)然后在内存中分组。将它改为在构建查询时使用iqueryable,GroupBy在服务器上执行,我只得到了我所期望的六行计数。
- 我想表达一下你的答案读起来有多容易。我在谷歌上读了三篇文章,然后才发现你很容易理解,非常简单地解释了这些差异。谢谢您!+ 1
- 很好的解释。在任何情况下,IEnumerable比iqueryable更可取吗?
- 当涉及多个实体时如何?( select new { Student, Teacher }我不能返回匿名类型的iQueryable。
- @坎迪丘那就用var。它不会"失去""IQueryableness"
- 所以我们可以说,如果我们使用iQueryable来查询内存对象,那么iNumerable和iQueryable之间就没有什么区别了?
- 可以这样说吗:在第一个查询(您的)中,SQL正在运行整个操作,包括WHERE子句,并且只返回相关行,,,,而第二个查询则选择了***…然后将**所有行返回到c中,然后进行筛选?
- @罗伊纳米尔,是的。
- 这是一个令人惊奇的答案,但在这种情况下,我迷失在另一件事上,那就是为什么我应该使用IEnumerable?
- 警告:由于所述的优化,IQueryable可能是一个诱人的解决方案,但不应允许它超过存储库或服务层。这是为了保护您的数据库免受"堆叠LINQ表达式"造成的开销。
- 是的。如果需要对原始结果(几个最终结果)进行重复筛选。在iqueryable接口上这样做将使数据库来回运行几次,在iEnumerable上这样做将在内存中进行过滤,从而使数据库更快(除非数据量很大)。
- 选择IEnumerable而不是IQueryable的另一个原因是并非所有的LINQ提供者都支持所有的LINQ操作。因此,只要您知道自己在做什么,就可以使用IQueryable将尽可能多的查询推送到linq提供者(linq2sql、ef、nhibernate、mongodb等)。但是,如果您让其他代码对您的IQueryable做任何它想做的事情,您最终会遇到麻烦,因为某个地方的一些客户机代码使用了不受支持的操作。我同意不将IQueryable的"野生"版本发布到存储库或等效层之后的建议。
- 阿维斯的评论不容忽视。这是@ali issa提出的疑问的答案。使用IQueryable意味着您只能做提供者可以做的事情(转换为SQL)。在此之后,您可能希望执行数据库不支持的操作。
- 我附议了@tarik关于这两种类型在内存中对象的使用的澄清请求。是否没有实际区别?比如说,我有一个列表字典,我想把列表平展成一个,但随后我将按一个或多个条件过滤它。我应该使用access作为可查询的还是可枚举的?
- @我只是想理解你的解释。但我在SQL配置文件中发现了2次到SQL Server的往返,即使我使用IEnumerable。你知道为什么吗?这是代码collabedit.com/nwtvj。
- @一旦开始迭代,您将得到一次往返。首先你前臂超过B(一次往返),然后你做A.take(3),这是另一次往返。(采取做迭代)
- @实际上,重新阅读你的代码——现在我明白你想要什么了。您应该在第一行调用.tolist()并声明为list。然后你将在记忆中完成剩下的工作。
最上面的答案是好的,但它没有提到解释两个接口"如何"不同的表达式树。基本上,有两组相同的LINQ扩展。Where()、Sum()、Count()、FirstOrDefault()等都有两个版本:一个接受函数,一个接受表达式。
您可能一直在使用这两种方法而没有意识到这一点,因为它们都是使用相同的语法来调用的:
例如,Where(x => x.City =="")在IEnumerable和IQueryable上工作。
为什么要麻烦这个表达树的事情?我只想让Where()过滤我的数据。主要原因是,EF和Linq2SQL窗体都可以将表达式树直接转换为SQL,在SQL中,代码执行速度更快。
哦,这听起来像是一个免费的性能提升,在这种情况下,我应该到处使用AsQueryable()?不,只有当基础数据提供者可以对它做些什么时,IQueryable才有用。把普通的List转换成IQueryable不会给你带来任何好处。
- 当被接受的答案被阅读和理解时,这是下一次必须阅读的答案。
- 依我看,这比公认的答案好。但是,我没有得到一件事:iqueryable对常规对象没有任何好处,好吧,但是它在任何方面更糟吗?因为如果它不能带来任何好处,那么选择IEnumerable还不够,所以在整个地方使用iqueryable的想法仍然有效。
- Sergey,iqueryable扩展了IEnumerable,所以当使用iqueryable时,您可以将比IEnumerable实例化更多的内容加载到内存中!所以这里有一个论点。(StdPox.com /问题/ 12064828 / & Helip;C++,虽然我认为我可以推断出这一点)
是的,两者都使用延迟执行。让我们用SQL Server事件探查器来说明区别….
当我们运行以下代码时:
1 2 3 4 5 6 7 8
| 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中,我们发现一个命令等于:
1
| "SELECT * FROM [dbo].[WebLog]" |
对一个有100万条记录的weblog表运行该代码块大约需要90秒。
因此,所有表记录都作为对象加载到内存中,然后在每个.where()中,它将是内存中针对这些对象的另一个过滤器。
当我们在上面的示例(第二行)中使用IQueryable而不是IEnumerable时:
在SQL Server Profiler中,我们发现一个命令等于:
1
| "SELECT TOP 1 * FROM [dbo].[WebLog] WHERE [DurationSeconds] > 10 AND [WebLogID] > 100 AND LEN([EmailAddress]) > 11" |
使用IQueryable运行此代码块大约需要4秒钟。
iQueryable有一个名为Expression的属性,它存储一个树表达式,当我们在示例中使用result时,该表达式开始被创建(称为延迟执行),最后,该表达式将被转换为SQL查询以在数据库引擎上运行。
- 我觉得在被接受的答案旁边阅读这个解释很重要。
- 当强制转换为IEnumerable时,下面的IQueryable会丢失它的IQueryable扩展方法。
- 使用SQL事件探查器的好主意……我对那个坏小子的使用还不够!!
两个都会让你延期执行,是的。
至于哪个比另一个更受欢迎,这取决于基础数据源是什么。
返回IEnumerable将自动强制运行时使用linq to对象查询集合。
返回一个IQueryable(顺便说一下,它实现了IEnumerable)提供了额外的功能,可以将查询转换为对底层源(linq to sql、linq to xml等)性能更好的查询。
一般而言,我建议如下:
想象一下一个IQueryable是什么,它是一个数据的"查询"(如果你想的话,可以对其进行优化)。IEnumerable是一组对象(已接收或已创建),您可以对其进行枚举。
通常,您希望保留查询的原始静态类型,直到它变得重要为止。
因此,您可以将变量定义为"var",而不是IQueryable<>或IEnumerable<>,您将知道您没有更改类型。
如果你从一个IQueryable<>开始,你通常想把它作为一个IQueryable<>保留,直到有令人信服的理由改变它。这样做的原因是,您希望向查询处理器提供尽可能多的信息。例如,如果您只使用10个结果(您已经调用了Take(10)),那么您希望SQL Server知道这一点,以便它可以优化其查询计划并只向您发送将要使用的数据。
将类型从IQueryable<>更改为IEnumerable<>的一个令人信服的原因可能是您正在调用一些扩展函数,而在您的特定对象中,IQueryable<>的实现既不能处理,也不能处理效率低下。在这种情况下,您可能希望将该类型转换为IEnumerable<>(例如,通过分配给IEnumerable<>类型的变量或使用AsEnumerable扩展方法),以便您调用的扩展函数最终成为Enumerable类中的扩展函数,而不是Queryable类中的扩展函数。
前面已经说过很多,但要回到根源上来,用一种更技术的方式:
IEnumerable是内存中可以枚举的对象的集合—内存中的序列使迭代成为可能(使在foreach循环中很容易进行,尽管您只能使用IEnumerator循环)。它们仍然存在于记忆中。
IQueryable是一个表达式树,在某个时刻,它将被转换成另一个具有枚举最终结果能力的东西。我想这就是让大多数人困惑的原因。
它们显然有不同的含义。
IQueryable表示一个表达式树(一个简单的查询),一旦调用了发布API,底层查询提供者就会将它转换成其他的东西,比如linq聚合函数(sum、count等)或tolist[数组、字典等]。而IQueryable对象也实现了IEnumerable和IEnumerable,这样,如果它们表示一个查询,则可以迭代该查询的结果。这意味着iQueryable不必只是查询。正确的说法是它们是表达树。
现在,这些表达式是如何执行的,以及它们变成什么,都取决于所谓的查询提供者(我们可以认为是表达式执行者)。
在实体框架世界(即神秘的基础数据源提供程序或查询提供程序)中,IQueryable表达式被转换为本地T-SQL查询。Nhibernate对它们做了类似的事情。您可以按照Linq中非常好地描述的概念编写自己的一个:例如,构建一个IQueryable提供者链接,并且您可能希望为产品存储提供者服务提供一个定制的查询API。
因此,基本上,IQueryable对象一直在构建,直到我们显式地释放它们,并告诉系统将它们重写为SQL或其他类型,然后发送执行链进行后续处理。
就像延迟执行一样,只要针对序列调用某些API(相同的计数、tolist等),就可以在内存中保留表达式树方案,并按需将其发送到执行中,这是LINQ功能。
两者的正确使用在很大程度上取决于您在特定情况下所面临的任务。对于众所周知的存储库模式,我个人选择返回IList,即IEnumerableover list(索引器等)。所以我的建议是只在存储库中使用IQueryable,在代码中的任何其他地方使用ienumerable。没有提到可测试性的问题,IQueryable崩溃并破坏了关注点分离原则。如果您从存储库中返回一个表达式,那么用户可能会按照自己的意愿使用持久层。
有点混乱:(来自评论中的讨论)它们中没有一个是内存中的对象,因为它们本身不是真正的类型,它们是类型的标记——如果你想深入了解的话。但把IEnumerable看作内存集合而把iqueryables看作表达式树是有意义的(这就是为什么甚至msdn都这么说的原因)。重点是iQuery接口继承了IEnumerable接口,因此如果它表示一个查询,则可以枚举该查询的结果。枚举导致执行与IQueryable对象关联的表达式树。因此,实际上,如果没有内存中的对象,就不能真正调用任何IEnumerable成员。如果你这样做,不管怎样,如果它不是空的,它会进入那里。iqueryables只是查询,而不是数据。
- IEnumerables始终在内存中的注释并非必然正确。iQuery接口实现IEnumerable接口。因此,可以将表示Linq-to-SQL查询的原始IQueryable传递到需要IEnumerable的视图中!您可能会惊讶地发现您的数据上下文已经过期,或者您最终会遇到与mars(多个活动结果集)有关的问题。
- 因此,实际上,如果没有内存中的对象,就不能真正调用任何IEnumerable成员。如果你这样做,不管怎样,如果它不是空的,它会进入那里。iqueryables只是查询,而不是数据。但我真的明白你的意思。我要对此发表评论。
- @Alexanderpritchard它们中没有一个是内存中的对象,因为它们本身不是真正的类型,它们是类型的标记——如果你想深入的话。但把IEnumerable看作内存集合而把iqueryables看作表达式树是有意义的(这就是为什么甚至msdn都这么说的原因)。重点是iQuery接口继承了IEnumerable接口,因此如果它表示一个查询,则可以枚举该查询的结果。枚举导致执行与IQueryable对象关联的表达式树。
有一篇博文简要介绍了使用IEnumerable会如何显著影响linq查询性能:实体框架:iqueryable与IEnumerable。
如果我们深入挖掘并深入研究其来源,我们可以看到,对于IEnumerable有明显不同的扩展方法:
1 2 3 4 5 6 7 8 9 10 11 12 13
| // 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| // 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源中指定。
这些是IQueryable和IEnumerable之间的一些区别。
我最近遇到一个问题,与IEnumerablev.IQueryable。首先使用的算法执行IQueryable查询以获得一组结果。然后将这些项传递给foreach循环,并将这些项实例化为实体框架(ef)类。然后在linq to entity查询的from子句中使用了这个ef类,导致结果是IEnumerable。
我对实体的ef和linq还比较陌生,所以花了一段时间才弄清楚瓶颈是什么。使用小型分析,我找到了查询,然后将所有单个操作转换为单个IQueryablelinq for entities查询。IEnumerable用了15秒,IQueryable用了0.5秒执行。涉及到三个表,阅读后,我认为IEnumerable查询实际上形成了一个三表交叉积,并过滤了结果。
尝试使用iqueryables作为经验法则,并对您的工作进行概要分析,以使您的更改是可测量的。
- 原因是IQueryable表达式在EF中转换为本机SQL,并在IEnumerable列表位于内存对象中时在数据库中执行。当您将诸如count、sum或any等聚合函数调用为…然后在记忆中操作。一旦调用了这些API中的一个,IQueryable也会卡在内存中,但如果没有,则可以将表达式向上传递到层的堆栈中,并在调用API之前使用过滤器。设计良好的DAL作为一个设计良好的存储库可以解决这类问题;)
我想澄清一些事情,由于似乎冲突的反应(主要围绕IEnumerable)。
(1)IQueryable扩展IEnumerable接口。(你可以发送一个IQueryable给某个需要IEnumerable而没有错误的东西。)
(2)当遍历结果集时,IQueryable和IEnumerablelinq都尝试延迟加载。(请注意,可以在每种类型的接口扩展方法中看到实现。)
换言之,IEnumerables并不是唯一的"内存"。IQueryables并不总是在数据库上执行。IEnumerable必须将内容加载到内存中(一旦检索到内容,可能会延迟),因为它没有抽象数据提供程序。IQueryables依赖抽象提供程序(如linq to sql),尽管它也可以是.NET内存中的提供程序。
样品使用案例
(a)从EF上下文中检索作为IQueryable的记录列表。(内存中没有记录。)
(b)将IQueryable传递给模型为IEnumerable的视图。(有效的。IQueryable扩展IEnumerable。
(C)迭代并从视图访问数据集的记录、子实体和属性。(可能导致例外!)
可能的问题
(1)IEnumerable尝试延迟加载,您的数据上下文已过期。由于提供程序不再可用,引发异常。
(2)实体框架实体代理已启用(默认),并且您尝试访问具有过期数据上下文的相关(虚拟)对象。与(1)相同。
(3)多个活动结果集(mars)。如果在foreach( var record in resultSet )块中迭代IEnumerable并同时尝试访问record.childEntity.childProperty,则可能会由于数据集和关系实体的延迟加载而以mars结束。如果未在连接字符串中启用,这将导致异常。
解决方案
- 我发现在连接字符串中启用mars不可靠。我建议你避开火星,除非它被很好的理解和明确的要求。
通过调用resultList = resultSet.ToList()来执行查询和存储结果,这似乎是确保实体在内存中最直接的方法。
在您访问相关实体的情况下,您可能仍然需要数据上下文。或者,您可以禁用实体代理,并从您的DbSet中显式地禁用与Include相关的实体。
"IEnumerable"和"IQueryable"之间的主要区别在于过滤器逻辑的执行位置。一个在客户端(内存中)执行,另一个在数据库上执行。
例如,我们可以考虑一个例子,在我们的数据库中,一个用户有10000条记录,假设只有900条记录是活动用户,所以在这种情况下,如果我们使用"IEnumerable",那么首先它将所有10000条记录加载到内存中,然后对其应用isactive过滤器,最后返回900个活动用户。
另一方面,同样的情况下,如果我们使用"iQueryable",它将直接在数据库上应用isactive过滤器,直接从数据库返回900个活动用户。
参考链路
- 哪一个在性能上优化且重量轻?
- @山姆"iqueryable"是更喜欢在优化和重量轻。
除了前2个非常好的答案(由Dris和Jacob提供):
IEnumerable
interface is in the System.Collections namespace.
IEnumerable对象表示内存中的一组数据,只能向前移动此数据。IEnumerable对象表示的查询被立即完全执行,因此应用程序可以快速接收数据。
执行查询时,IEnumerable将加载所有数据,如果需要对其进行筛选,则筛选本身在客户端完成。
IQueryable interface is located in the System.Linq namespace.
IQueryable对象提供对数据库的远程访问,并允许您以从开始到结束的直接顺序或相反的顺序浏览数据。在创建查询的过程中,返回的对象是IQueryable,对查询进行了优化。因此,在执行过程中消耗的内存更少,网络带宽更少,但同时,它的处理速度比返回IEnumerable对象的查询稍慢。
选择什么?
如果您需要整个返回的数据集,那么最好使用IEnumerable,它提供了最大的速度。
如果您不需要整个返回数据集,而只需要一些过滤后的数据,那么最好使用iqueryable。
我们可以以相同的方式使用这两种方法,而且它们在性能上只是不同的。
IQueryable仅以有效的方式对数据库执行。这意味着它创建了一个完整的select查询,只获取相关的记录。
例如,我们想把名字以"nimal"开头的前10位客户作为例子。在这种情况下,选择查询将生成为select top 10 * from Customer where name like ‘Nimal%’。
但是,如果我们使用IEnumerable,查询将类似于select * from Customer where name like ‘Nimal%’,前十个查询将在C编码级别进行筛选(它从数据库中获取所有客户记录并将其传递到C)。
IEnumrable将数据存储到内存中
但如果是iqueuable,它不会存储在内存中。
有关详细信息,请使用SQL事件探查器进行检查
第一次击中你使用iQueryable查询并查看执行的查询
然后尝试从IEnumable
- 你在这里说的话没有多大意义。两者都是接口,接口不规定任何实现,因此不做任何事情。除此之外,这个问题真的需要另一个答案吗?