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

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


当前回答

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

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

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

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

其他回答

一般来说,您希望保留查询的原始静态类型,直到有必要。

出于这个原因,你可以将你的变量定义为'var',而不是IQueryable<>或IEnumerable<>,你会知道你没有改变类型。

如果您开始使用IQueryable<>,通常希望将其保留为IQueryable<>,直到有一些令人信服的理由更改它。这样做的原因是您希望为查询处理器提供尽可能多的信息。例如,如果你只打算使用10个结果(你已经调用Take(10)),那么你想让SQL Server知道这一点,这样它就可以优化它的查询计划,只向你发送你将使用的数据。

A compelling reason to change the type from IQueryable<> to IEnumerable<> might be that you are calling some extension function that the implementation of IQueryable<> in your particular object either cannot handle or handles inefficiently. In that case, you might wish to convert the type to IEnumerable<> (by assigning to a variable of type IEnumerable<> or by using the AsEnumerable extension method for example) so that the extension functions you call end up being the ones in the Enumerable class instead of the Queryable class.

是的,两者都使用延迟执行。让我们使用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查询,在数据库引擎上运行。

除了上面的,有趣的是,如果你使用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不能为负值。

总的来说,我建议以下几点:

如果您想让开发人员使用您的方法在执行之前细化您返回的查询,则返回IQueryable<T>。 如果您想传输一组对象来枚举,则返回IEnumerable。

想象一下IQueryable是什么——一个数据的“查询”(如果你想,你可以对它进行细化)。IEnumerable是一组对象(已经接收到或创建了),可以对其进行枚举。

上面的答案很好,但它没有提到解释两个接口“如何”不同的表达式树。基本上,有两组相同的LINQ扩展。Where(), Sum(), Count(), FirstOrDefault()等都有两个版本:一个接受函数,一个接受表达式。

IEnumerable版本签名为:Where(Func<Customer, bool> predicate) IQueryable版本签名是:Where(表达式<Func<Customer, bool>>谓词)

你可能在没有意识到的情况下使用了这两个函数,因为它们使用相同的语法调用:

例如,Where(x => x.City == "<City>")对IEnumerable和IQueryable都有效

当在IEnumerable集合上使用Where()时,编译器将一个编译后的函数传递给Where() 当在IQueryable集合上使用Where()时,编译器将表达式树传递给Where()。表达式树类似于反射系统,但用于代码。编译器将代码转换为数据结构,该结构以易于理解的格式描述代码的功能。

为什么要用这个表达式树呢?我只想让Where()过滤我的数据。 主要原因是EF和Linq2SQL orm都可以将表达式树直接转换为SQL,这样您的代码将执行得更快。

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