为什么不应该混合使用IQueryable和IEnumerable查询?

时间:2019-05-13 07:25:01

标签: c# linq

在linq中进行查询时,建议不要合并iqeuryableienumerable部分。仅仅是性能问题还是什么问题?

我想澄清一下。有时候这样做是不可行的(就像@Harald Coppoolse在他的回答中解释的那样。但是有时候它是可行的,但是我不确定性能如何会发生变化。 假设此sudo代码:

'from e in someIEnumerable
join q in someIQueryable on e.reference equals q.ID'

那么此查询将如何处理?

3 个答案:

答案 0 :(得分:1)

在LinQ to Entities中

IQueryable扩展了IEnumerable,仅仅是一个查询表达式。这意味着直到例如.ToList()FirstOrDefault()才会解析实体数据存储中的数据。

IEnumerable提供了GetEnumerator()方法,允许迭代已经解决的集合。

因此,应该使用IQueryable直到数据访问层,然后在已经获取数据时使用IEnumerable

使用适当的接口是一种不获取所有不需要数据的方法,并且仅对您需要处理的数据进行处理(例如重复)。

基于JonSkeet答案进行了编辑

答案 1 :(得分:1)

要确定是否继续使用IEnumerable的IQueryable LINQ语句,重要的是要了解两者之间的区别。

实现IEnumerable<...>的类的对象是表示序列的对象。它保留了其中的所有内容,以获取序列的第一个元素,一旦有了一个元素,就可以要求下一个,如果有下一个元素。

如果您使用IEnumerable.GetEnumerator()IEnumerator.MoveNext(),则显式开始枚举;或者使用foreachToList()ToDictionary(),{{1 }},FirstOrDefault()Sum()Count()等。如果您调查source code of Enumerable,您会发现他们深处称为Any()和{{ 1}}

尽管实现GetEnumator()的类的对象也表示一个序列,但它不必知道如何枚举此序列。它包含一个MoveNext()和一个IQueryable<...>Expression是查询数据的一种非常通用的形式,Provider知道谁必须执行查询(通常是数据库管理系统)以及用于与该执行程序进行通信的语言(通常是SQL)。 -))。

Expression对象与也返回Provider的方法连接起来,只会更改IQueryable;未联系数据库。

当您调用IQueryable开始枚举可查询的内容时,Expression将发送到Queryable.GetEnumerator(),后者将将此Expression转换为SQL并查询执行程序。返回的数据具体化为IEnumerable序列。调用Provider,并返回返回的枚举数,因此您可以像调用枚举Expression一样调用GetEnumerator()MoveNext()

由于必须将Current转换为SQL,因此不能像使用IEnumerable那样使用IQuueryable来完成所有工作。

以下将适用于IEnumerable:

IEnumerable

编译器无法检测到以下任何问题:

Expression

尽管可以编译,但是您会得到一个运行时异常,告诉您double CalculateValueAddedTax(Price p) {...} IEnumerable<OrderLine> orderLines = ... decimal totalValueAddedTax = orderLines .Where(orderLine => orderLine.HasValueAddedTax) .Select(orderLine => CaculateValueAddedTax(orderLine.Price)) .Sum(); 无法转换为SQL。实际上,实体框架不支持很多LINQ函数。参见Supported and Unsupported LINQ methods (LINQ to entities)

正确的解决方案是将本地函数转换为支持的LINQ表达式的串联。如果无法执行此操作,则必须先在IQueryable<OrderLine> orderLines = ... decimal totalValueAddedTax = orderLines .Where(orderLine => orderLine.HasValueAddedTax) .Select(orderLine => CaculateValueAddedTax(orderLine.Price)) .Sum(); 之前执行查询的一部分,然后将其设为本地可枚举对象,然后才能使用它。

CalculateValueAddedTax

尽管这行得通,但是如果您仅打算使用其中一些数据,例如,如果您使用CalculateValueAddedTax

,则获取所有数据将是一种浪费。
IQueryable<OrderLine> orderLines = ...
IEnumerable<Price> pricesWithValueAddedTaxes = orderLines
    .Where(orderLine => orderLine.HasValueAddedTax)
    .Select(orderLine => orderLine.Price)
    .ToList();
decimal totalValueAddedTax = pricesWithValueAddedTax
    .Select(price => CaculateValueAddedTax(price))
    .Sum();

转移所有美国人类,只接受第一个人类,将是浪费。因此发明了AsEnumerable

FirstOrDefault()

IQueryable<Human> queryAmericans = myDbContext.Humans .Where(human => human.Country == "USA") .OrderByDescending(human => human.Age); List<Human> americans = queryAmericans.ToList(); var oldestSpecialAmerican = americans .Where(american => american.IsSpecial()) .FirstOrDefault(); 的作用在某种程度上取决于Price result = orderLines .Where(orderLine => orderLine.HasValueAddedTax) .Select(orderLine => orderLine.Price) .OrderByDescending(price => price.Value) .AsEnumerable() .Select(price => CaculateValueAddedTax(price)) .FirstOrDefault(); ,但是聪明的Provider会“每页”查询数据,因此并不是所有要查询的数百万数据都是获取了,但是只有一部分,比如说25。如果您使用AsEnumerable,那么将会有一些获取没有,但至少没有获取。枚举第26个元素后,将立即获取下一页。页面大小是在获取过多数据和必须经常执行查询之间的折衷方案。

因此ProviderFirstOrDefault之间的主要区别在于,Enumerable将由本地进程执行:您可以调用的每个函数都可以由Enumerable执行。 AsEnumerable由外部进程执行。您调用的每个函数都必须转换为外部流程可以理解的语言,从而限制了您可以在查询中使用的函数。

编译器无法检测到外部进程使用哪种语言,也不会抱怨。如果使用不受支持的功能,则会出现异常。

回到您的问题:我应该使用AsEnumerable / AsQueryable

AsEnumerable

以上示例显示,有时您必须在继续LINQ-ing之前向本地进程查询部分数据。聪明的方法是使用AsEnumerable。

数据库查询的最慢部分之一是将选定的数据传输到本地进程。因此,如果您决定使用AsEnumerable,请尝试仅传输本地所需的数据:如果只想处理AsQueryable

,请不要传输完整的AsQueryable

此外,看看是否可以将本地函数更改为IQueryable。

OrderLines

现在,完整的查询可以由数据库管理系统执行:

Prices

可查询的##

如果您具有本地枚举数,IQuerayble<decimal> CalculateValueAddedTaxes(this IQueryable<Price> prices) { return prices.Select(price => price.VatPercentage * prive.Value); } 不会突然将数据传输到数据库。它的作用是创建一个var result = orderLines .Where(orderLine => orderLine.HasValueAddedTax) .Select(orderLine => orderLine.Price) .OrderByDescending(price => price.Value) .CalculateValueAddedTaxes() .FirstOrDefault(); 和一个AsQueryableExpression只是对输入数据的函数调用。就像任何可查询的一样,当您开始枚举时,Provider被发送到Expression。该Expression只会执行Provider,也就是说调用Provider

那我为什么要使用AsQueryable?

在极少数情况下,您将拥有本地可枚举序列,并且需要调用需要使用Expression作为输入的函数:

GetEnumerator

答案 2 :(得分:0)

我意识到,将IEnumerableIQueryable与join结合使用:

  1. 如果第一项为IEnumerable,第二项为IQueryable,则所有查询均为IEnumerableIQueryable部分首先执行并返回数据存储到内存中,然后查询继续。

  2. ,但是如果第一个术语为IQueryable,第二个术语为IEnumerable,则会抛出异常,因为第二个术语无法转换为IQueryable