在linq中进行查询时,建议不要合并iqeuryable
和ienumerable
部分。仅仅是性能问题还是什么问题?
我想澄清一下。有时候这样做是不可行的(就像@Harald Coppoolse在他的回答中解释的那样。但是有时候它是可行的,但是我不确定性能如何会发生变化。 假设此sudo代码:
'from e in someIEnumerable
join q in someIQueryable on e.reference equals q.ID'
那么此查询将如何处理?
答案 0 :(得分:1)
在LinQ to Entities中
IQueryable
扩展了IEnumerable
,仅仅是一个查询表达式。这意味着直到例如.ToList()
或FirstOrDefault()
才会解析实体数据存储中的数据。
IEnumerable
提供了GetEnumerator()
方法,允许迭代已经解决的集合。
因此,应该使用IQueryable
直到数据访问层,然后在已经获取数据时使用IEnumerable
。
使用适当的接口是一种不获取所有不需要数据的方法,并且仅对您需要处理的数据进行处理(例如重复)。
基于JonSkeet答案进行了编辑
答案 1 :(得分:1)
要确定是否继续使用IEnumerable的IQueryable LINQ语句,重要的是要了解两者之间的区别。
实现IEnumerable<...>
的类的对象是表示序列的对象。它保留了其中的所有内容,以获取序列的第一个元素,一旦有了一个元素,就可以要求下一个,如果有下一个元素。
如果您使用IEnumerable.GetEnumerator()
和IEnumerator.MoveNext()
,则显式开始枚举;或者使用foreach
,ToList()
,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个元素后,将立即获取下一页。页面大小是在获取过多数据和必须经常执行查询之间的折衷方案。
因此Provider
和FirstOrDefault
之间的主要区别在于,Enumerable将由本地进程执行:您可以调用的每个函数都可以由Enumerable执行。 AsEnumerable
由外部进程执行。您调用的每个函数都必须转换为外部流程可以理解的语言,从而限制了您可以在查询中使用的函数。
编译器无法检测到外部进程使用哪种语言,也不会抱怨。如果使用不受支持的功能,则会出现异常。
回到您的问题:我应该使用AsEnumerable / AsQueryable
以上示例显示,有时您必须在继续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();
和一个AsQueryable
。 Expression
只是对输入数据的函数调用。就像任何可查询的一样,当您开始枚举时,Provider
被发送到Expression
。该Expression
只会执行Provider
,也就是说调用Provider
。
那我为什么要使用AsQueryable?
在极少数情况下,您将拥有本地可枚举序列,并且需要调用需要使用Expression
作为输入的函数:
GetEnumerator
答案 2 :(得分:0)
我意识到,将IEnumerable
和IQueryable
与join结合使用:
如果第一项为IEnumerable
,第二项为IQueryable
,则所有查询均为IEnumerable
,IQueryable
部分首先执行并返回数据存储到内存中,然后查询继续。
,但是如果第一个术语为IQueryable
,第二个术语为IEnumerable
,则会抛出异常,因为第二个术语无法转换为IQueryable