Return 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;
Return 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;
Answers:
是的,两者都会使您推迟执行。
区别在于,IQueryable<T>
该接口允许LINQ-to-SQL(实际上是LINQ-to-anything)正常工作。因此,如果您进一步优化了上的查询IQueryable<T>
,则该查询将尽可能在数据库中执行。
对于这种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);
这是一个非常重要的区别,IQueryable<T>
在许多情况下进行操作可以使您避免从数据库返回太多行。另一个主要的示例是进行分页:如果使用on Take
和Skip
on IQueryable
,则将仅获得请求的行数;在上执行此操作IEnumerable<T>
将导致您的所有行都加载到内存中。
IEnumerable
来IQueryable
是不是所有的LINQ操作都是由所有LINQ提供支持。因此,只要您知道自己在做什么,就可以使用IQueryable
将尽可能多的查询推送到LINQ提供程序(LINQ2SQL,EF,NHibernate,MongoDB等)。但是,如果您让其他代码随心所欲地做任何事情,您IQueryable
最终将陷入困境,因为某些客户端代码使用了不受支持的操作。我同意不要将IQueryable
s放到存储库或等效层上的建议。
最佳答案是好的,但没有提到解释两个接口“如何”不同的表达式树。基本上,有两组相同的LINQ扩展。Where()
,Sum()
,Count()
,FirstOrDefault()
,等都有两个版本:一个接受函数和一个接受表达式。
该IEnumerable
版本的签名是:Where(Func<Customer, bool> predicate)
该IQueryable
版本的签名是:Where(Expression<Func<Customer, bool>> predicate)
您可能一直在使用这两种方法而没有意识到,因为两者都使用相同的语法来调用:
例如Where(x => x.City == "<City>")
在两个IEnumerable
和IQueryable
在集合Where()
上使用时IEnumerable
,编译器将已编译的函数传递给Where()
在集合Where()
上使用时IQueryable
,编译器将表达式树传递给Where()
。表达式树类似于反射系统,但用于代码。编译器将您的代码转换为数据结构,该数据结构以易于消化的格式描述代码的功能。
为什么要打扰这个表达树呢?我只想Where()
过滤我的数据。
主要原因是EF和Linq2SQL ORM都可以将表达式树直接转换为SQL,从而使您的代码执行得更快。
哦,这听起来像是免费的性能提升,AsQueryable()
在这种情况下,我应该在所有地方使用它吗?
否,IQueryable
仅当基础数据提供者可以执行某些操作时才有用。将类似常规的东西转换List
为IQueryable
不会带来任何好处。
IQueryable
它不会像解析函数那样进行解析IEnumerable
:例如,如果您想知道a DBSet<BookEntity>
中的哪些元素不在a中List<BookObject>
,dbSetObject.Where(e => !listObject.Any(o => o.bookEntitySource == e))
则会抛出异常:Expression of type 'BookEntity' cannot be used for parameter of type 'BookObject' of method 'Boolean Contains[BookObject] (IEnumerable[BookObject], BookObject)'
。我必须在.ToList()
之后加上dbSetObject
。
是的,两者都使用延迟执行。让我们来说明使用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查询以在数据库引擎上运行。
两者都会让您推迟执行,是的。
至于哪个优先于另一个,则取决于您的基础数据源是什么。
返回an IEnumerable
将自动迫使运行时使用LINQ to Objects查询您的集合。
返回IQueryable
(IEnumerable
通过顺便实现)提供了额外的功能,可以将您的查询转换为可能在基础源上更好地执行的查询(LINQ to SQL,LINQ to XML等)。
一般来说,我建议以下内容:
返回IQueryable<T>
如果你想用你的方法来完善你在执行前返回查询,让开发人员。
IEnumerable
如果要传输一组要枚举的对象,则返回。
想象一下IQueryable
它是什么-数据查询(可以根据需要进行优化)。An IEnumerable
是一组可以枚举的对象(已经收到或创建)。
之前已经说了很多话,但是以一种更为技术性的方式回到了根源:
IEnumerable
是内存中可以枚举的对象的集合 -一个内存中的序列,可以循环访问(使循环内的访问变得容易foreach
,尽管IEnumerator
只能使用)。它们原样驻留在内存中。IQueryable
是一个表达树,它将在某个时刻被转换成其他东西,并能够枚举最终结果。我想这就是让大多数人困惑的地方。它们显然具有不同的含义。
IQueryable
表示一个表达式树(简单地说是一个查询),一旦调用发布API,该表达式树将被基础查询提供程序转换为其他内容,例如LINQ聚合函数(Sum,Count等)或ToList [Array,Dictionary,。 ..]。而且IQueryable
对象也实现IEnumerable
,IEnumerable<T>
因此,如果它们表示查询,则可以迭代该查询的结果。这意味着IQueryable不必仅是查询。正确的说法是它们是表达树。
现在,如何执行这些表达式以及将这些表达式变成什么都取决于所谓的查询提供程序(我们可以想到的表达式执行程序)。
在Entity Framework世界中(这是神秘的基础数据源提供程序或查询提供程序),IQueryable
表达式被转换为本地T-SQL查询。Nhibernate
与他们做类似的事情。您可以按照LINQ中描述的概念编写您自己的概念:例如,构建IQueryable Provider链接,并且您可能想要为产品商店提供程序服务使用自定义查询API。
因此,基本上,IQueryable
对象一直都在构造,直到我们明确释放它们,并告诉系统将它们重写为SQL或其他内容,然后向下发送执行链以进行后续处理。
好像推迟执行一样LINQ
,每当对序列调用某些API(相同的Count,ToList等)时,它的一个功能就是将表达式树方案保留在内存中,仅在需要时才将其发送到执行中。
两者的正确用法在很大程度上取决于您针对特定情况所面临的任务。对于众所周知的存储库模式,我个人选择返回IList
,即IEnumerable
在List(索引器等)之上。因此,我的建议是IQueryable
仅在存储库中使用,并在代码中的其他任何地方使用IEnumerable。更不用说IQueryable
分解和破坏关注点分离原则的可测试性关注点了。如果您从存储库中返回表达式,则使用者可以按照他们的意愿使用持久层。
除了混乱之外,还有一点点补充:)(来自评论中的讨论))它们都不是内存中的对象,因为它们本身不是真正的类型,它们是类型的标记-如果您想深入了解。但是将IEnumerables视为内存中的集合,而将IQueryables视为表达式树是有道理的(这也是MSDN这样说的原因)。关键是IQueryable接口继承了IEnumerable接口,因此,如果它表示查询,则可以枚举该查询的结果。枚举使与IQueryable对象关联的表达式树得以执行。因此,实际上,如果没有对象在内存中,就无法真正调用任何IEnumerable成员。无论如何,如果不是空的,它将进入那里。IQueryables只是查询,而不是数据。
通常,您要保留查询的原始静态类型,直到它变得重要为止。
因此,您可以将变量定义为'var'而不是IQueryable<>
or,IEnumerable<>
并且您将知道您没有更改类型。
如果您以开头IQueryable<>
,则通常希望将其保留为,IQueryable<>
直到有令人信服的理由进行更改为止。这样做的原因是,您希望为查询处理器提供尽可能多的信息。例如,如果您只打算使用10个结果(称为Take(10)
),那么您希望SQL Server知道这一点,以便它可以优化其查询计划并仅向您发送要使用的数据。
一个令人信服的理由来改变从类型IQueryable<>
到IEnumerable<>
可能是你正在调用一些扩展功能的实现IQueryable<>
在您的特定对象既不能处理或把手低效。在这种情况下,您可能希望将类型转换为IEnumerable<>
(例如,通过分配给类型的变量IEnumerable<>
或通过使用AsEnumerable
扩展方法),以便最终调用的扩展函数是Enumerable
类中的类而不是Queryable
类中的那些。
有一篇博客文章,其中包含简短的源代码示例,内容涉及如何滥用它们IEnumerable<T>
会严重影响LINQ查询性能:实体框架: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) }));
}
}
第一个返回可枚举的迭代器,第二个通过在IQueryable
源中指定的查询提供程序创建查询。
我最近遇到IEnumerable
v 的问题IQueryable
。首先使用的算法执行IQueryable
查询以获得一组结果。然后将这些传递到foreach
循环,并将这些项实例化为实体框架(EF)类。然后,此EF类用于from
Linq to Entity查询的子句中,导致结果为IEnumerable
。
我对EF和Linq for Entities还是很陌生,所以花了一段时间才弄清瓶颈所在。使用MiniProfiling,我找到了查询,然后将所有单个操作转换为单个IQueryable
Linq for Entities查询。执行IEnumerable
花费了15秒,IQueryable
花费了0.5秒。涉及到三个表,阅读本文后,我相信IEnumerable
查询实际上是在形成三个表的叉积并过滤结果。
尝试将IQueryables用作经验法则,并对您的工作进行概要分析,以使更改可衡量。
IQueryable
一旦调用了这些API之一,它们也会卡在内存中,但是如果没有调用,则可以将表达式向上传递到各层堆栈中,并使用过滤器,直到调用API。设计良好的DAL作为设计良好的存储库将解决此类问题;)
我想澄清一些事情,因为看似矛盾的响应(主要是围绕IEnumerable)。
(1)IQueryable
扩展IEnumerable
接口。(您可以发送IQueryable
到期望IEnumerable
没有错误的内容。)
(2)IQueryable
和IEnumerable
LINQ在迭代结果集时都尝试延迟加载。(请注意,可以在每种类型的接口扩展方法中看到实现。)
换句话说,IEnumerables
并不是排他的“内存中”。 IQueryables
并不总是在数据库上执行。 IEnumerable
必须将内容加载到内存中(一旦检索到,可能会延迟),因为它没有抽象数据提供程序。 IQueryables
依赖于抽象提供程序(例如LINQ-to-SQL),尽管它也可以是.NET内存提供程序。
样例用例
(a)IQueryable
从EF上下文中检索记录列表。(没有记录在内存中。)
(b)将传递IQueryable
给模型为的视图IEnumerable
。(有效。IQueryable
扩展IEnumerable
。)
(c)遍历并从视图访问数据集的记录,子实体和属性。(可能会导致异常!)
可能的问题
(1)IEnumerable
尝试延迟加载,并且您的数据上下文已过期。由于提供者不再可用而引发异常。
(2)实体框架实体代理已启用(默认设置),并且您尝试使用过期的数据上下文访问相关(虚拟)对象。与(1)相同。
(3)多个活动结果集(MARS)。如果您IEnumerable
在一个foreach( var record in resultSet )
块中进行迭代并同时尝试访问record.childEntity.childProperty
,则由于数据集和关系实体的延迟加载而可能会遇到MARS。如果未在您的连接字符串中启用它,则会导致异常。
解
通过调用执行查询并存储结果,resultList = resultSet.ToList()
这似乎是确保实体处于内存中的最直接方法。
如果您要访问相关实体,则可能仍需要数据上下文。要么,要么您可以从中禁用实体代理和显式Include
相关的实体DbSet
。
“ IEnumerable”和“ IQueryable”之间的主要区别在于执行过滤器逻辑的位置。一个在客户端(在内存中)执行,另一个在数据库上执行。
例如,我们可以考虑一个示例,在该示例中,数据库中有一个用户的10,000条记录,并且假设只有900个活动用户,因此在这种情况下,如果我们使用“ IEnumerable”,则首先将所有10,000条记录加载到内存中,然后在其上应用IsActive过滤器,最终返回900个活跃用户。
另一方面,在相同情况下,如果我们使用“ IQueryable”,它将直接在数据库上应用IsActive筛选器,直接从那里返回900个活动用户。
参考链接
我们可以以相同的方式使用这两种方法,它们的性能只是不同。
IQueryable仅以有效的方式对数据库执行。这意味着它将创建整个选择查询,并且仅获取相关记录。
例如,我们要选择名称以“ Nimal”开头的前10位客户。在这种情况下,选择查询将生成为select top 10 * from Customer where name like ‘Nimal%’
。
但是,如果我们使用IEnumerable,则查询将类似select * from Customer where name like ‘Nimal%’
,并且前十名将在C#编码级别进行过滤(它将从数据库中获取所有客户记录,并将它们传递到C#中)。
除了前两个非常好的答案(由driis和Jacob撰写):
IEnumerable接口在System.Collections命名空间中。
IEnumerable对象表示内存中的一组数据,并且只能向前移动该数据。由IEnumerable对象表示的查询将立即完整执行,因此应用程序可以快速接收数据。
执行查询时,IEnumerable会加载所有数据,并且如果需要过滤,则过滤本身是在客户端完成的。
IQueryable接口位于System.Linq命名空间中。
IQueryable对象提供对数据库的远程访问,并允许您以直接的顺序从头到尾或以相反的顺序浏览数据。在创建查询的过程中,返回的对象是IQueryable,查询已得到优化。结果,在执行过程中消耗的内存更少,网络带宽也更少,但是与此同时,它的处理速度比返回IEnumerable对象的查询要慢一些。
选择什么?
如果您需要整个返回的数据集,则最好使用IEnumerable,它可以提供最快的速度。
如果您不需要整个返回的数据集,而只需要一些过滤后的数据,那么最好使用IQueryable。
除上述内容外,有趣的是,如果您使用IQueryable
而不是,则可以获取异常IEnumerable
:
如果products
是IEnumerable
:
products.Skip(-4);
但是如果 products
是,IQueryable
并且它正在尝试访问数据库表中的记录,则会收到此错误:
OFFSET子句中指定的偏移量不能为负。
这是因为构造了以下查询:
SELECT [p].[ProductId]
FROM [Products] AS [p]
ORDER BY (SELECT 1)
OFFSET @__p_0 ROWS
和OFFSET不能为负值。