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

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


当前回答

上面的答案很好,但它没有提到解释两个接口“如何”不同的表达式树。基本上,有两组相同的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不会给你带来任何好处。

其他回答

是的,两者都使用延迟执行。让我们使用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<T>。 如果您想传输一组对象来枚举,则返回IEnumerable。

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

我们可以用同样的方法来使用它们,它们只是在性能上有所不同。

IQueryable仅以有效的方式对数据库执行。这意味着它创建了一个完整的选择查询,并且只获取相关的记录。

例如,我们想以名字以“Nimal”开头的前10名客户为例。在这种情况下,将生成select查询,从客户中选择名称为“Nimal%”的前10 *。

但是如果我们使用IEnumerable,查询就像select * from Customer,其中名称像' Nimal% ',前十位将在c#编码级别过滤(它从数据库中获取所有客户记录并将它们传递给c#)。

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

我想澄清一些事情,因为似乎相互矛盾的反应(主要围绕IEnumerable)。

IQueryable扩展了IEnumerable接口。(你可以发送一个IQueryable给一个期望IEnumerable而没有错误的对象。)

(2) IQueryable和IEnumerable LINQ在迭代结果集时都尝试延迟加载。(注意,可以在每种类型的接口扩展方法中看到实现。)

换句话说,IEnumerables不是独占的“内存中”。IQueryables并不总是在数据库上执行。IEnumerable必须将数据加载到内存中(一旦检索到,可能是惰性的),因为它没有抽象数据提供程序。IQueryables依赖于抽象提供者(如LINQ-to-SQL),尽管这也可以是. net内存中的提供者。

样例用例

(a)从EF上下文检索IQueryable记录列表。(内存中没有记录。)

(b)将IQueryable传递给模型为IEnumerable的视图。(有效。IQueryable扩展IEnumerable。)

(c)在视图中遍历并访问数据集的记录、子实体和属性。(可能会导致异常!)

可能的问题

IEnumerable尝试延迟加载,你的数据上下文过期了。由于提供程序不再可用而引发异常。

(2)实体框架实体代理已启用(默认),并且您尝试访问具有过期数据上下文的相关(虚拟)对象。同(1)。

(3)多活动结果集(MARS)。如果你在foreach(var record in resultSet)块中遍历IEnumerable,同时尝试访问record. childentity。childProperty,由于数据集和关系实体的惰性加载,您可能最终会得到MARS。如果在连接字符串中未启用该选项,则会导致异常。

解决方案

我发现在连接字符串中启用MARS工作不可靠。我建议你避免MARS,除非它被很好地理解和明确地渴望。

执行查询并通过调用resultList = resultSet.ToList()来存储结果,这似乎是确保实体位于内存中的最直接的方法。

在访问相关实体的情况下,可能仍然需要数据上下文。或者,您可以禁用实体代理,并显式地从DbSet中包含相关实体。