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

两者都将被延迟执行,何时应该优先于其中一个?


当前回答

是的,两者都使用延迟执行。让我们使用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运行这段代码大约需要4秒钟。

IQueryable有一个名为Expression的属性,它存储了一个树表达式,当我们在示例中使用结果时开始创建这个树表达式(称为延迟执行),在结束时,这个表达式将被转换为一个SQL查询,在数据库引擎上运行。

其他回答

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

IEnumerable 接口在系统中。集合名称空间。

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

当执行查询时,IEnumerable加载所有数据,如果我们需要对其进行筛选,筛选本身是在客户端完成的。

可查询接口位于系统中。Linq命名空间。

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

选择什么?

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

如果你不需要整个返回的数据集,而只是一些过滤后的数据,那么最好使用IQueryable。

之前已经说了很多,但回到根源,以一种更专业的方式:

IEnumerable是一个在内存中可以枚举的对象的集合——一个在内存中的序列,使得迭代成为可能(使得在每一个循环中都很容易,尽管你可以只使用IEnumerator)。它们就这样存在于记忆中。 IQueryable是一个表达式树,它将在某个时刻被转换成其他东西,并具有枚举最终结果的能力。我想这就是大多数人困惑的地方。

它们显然有着不同的内涵。

IQueryable表示一个表达式树(简单地说,就是一个查询),一旦调用了发布api,它就会被底层查询提供者转换成其他东西,比如LINQ聚合函数(Sum, Count等)或ToList[数组,字典,…]。IQueryable对象也实现了IEnumerable, IEnumerable<T>,因此如果它们表示一个查询,那么该查询的结果可以被迭代。这意味着IQueryable不只是查询。正确的术语是它们是表达式树。

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

在实体框架世界(即神秘的底层数据源提供程序或查询提供程序)中,IQueryable表达式被转换为本地T-SQL查询。Nhibernate对它们做了类似的事情。例如,您可以按照《LINQ:构建IQueryable Provider链接》中描述的概念编写自己的查询工具,并且您可能希望为您的产品存储提供者服务提供一个自定义查询API。

基本上,IQueryable对象一直在被构造直到我们显式地释放它们并告诉系统把它们重写成SQL或者其他什么然后发送到执行链进行后续处理。

就像延迟执行一样,它是一个LINQ特性,在内存中保留表达式树方案,并仅在需要时将其发送到执行中,无论何时针对序列调用某些api(相同的Count、ToList等)。

The proper usage of both heavily depends on the tasks you're facing for the specific case. For the well-known repository pattern I personally opt for returning IList, that is IEnumerable over Lists (indexers and the like). So it is my advice to use IQueryable only within repositories and IEnumerable anywhere else in the code. Not saying about the testability concerns that IQueryable breaks down and ruins the separation of concerns principle. If you return an expression from within repositories consumers may play with the persistence layer as they would wish.

A little addition to the mess :) (from a discussion in the comments)) None of them are objects in memory since they're not real types per se, they're markers of a type - if you want to go that deep. But it makes sense (and that's why even MSDN put it this way) to think of IEnumerables as in-memory collections whereas IQueryables as expression trees. The point is that the IQueryable interface inherits the IEnumerable interface so that if it represents a query, the results of that query can be enumerated. Enumeration causes the expression tree associated with an IQueryable object to be executed. So, in fact, you can't really call any IEnumerable member without having the object in the memory. It will get in there if you do, anyways, if it's not empty. IQueryables are just queries, not the data.

除了上面的,有趣的是,如果你使用IQueryable而不是IEnumerable,你可以得到异常:

如果products是一个IEnumerable,下面的工作很好:

products.Skip(-4);

然而,如果products是一个IQueryable对象,并且它试图从DB表中访问记录,那么你会得到这个错误:

offset子句中指定的偏移量不能为负。

这是因为构造了以下查询:

SELECT [p].[ProductId]
FROM [Products] AS [p]
ORDER BY (SELECT 1)
OFFSET @__p_0 ROWS

OFFSET不能为负值。

是的,两者都使用延迟执行。让我们使用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运行这段代码大约需要4秒钟。

IQueryable有一个名为Expression的属性,它存储了一个树表达式,当我们在示例中使用结果时开始创建这个树表达式(称为延迟执行),在结束时,这个表达式将被转换为一个SQL查询,在数据库引擎上运行。

都能让你延期执行,没错。

至于哪个是首选的,这取决于您的底层数据源是什么。

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

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