用户创建 LINQ 查询后,它将转换为命令树。 命令树是与 Entity Framework 兼容的查询的表示形式。 然后,对数据源执行命令树。 在查询执行时,将计算所有查询表达式(即查询的所有组件),包括用于结果具体化的表达式。
执行查询表达式时可能会有所不同。 LINQ 查询总是在查询变量被遍历时执行,而不是在查询变量创建时执行。 这称为 延迟执行。 还可以强制查询立即执行,这对于缓存查询结果很有用。 本主题稍后将对此进行介绍。
执行 LINQ to Entities 查询时,查询中的某些表达式可能在服务器上执行,某些部分可能在客户端本地执行。 表达式的客户端计算发生于在服务器上执行查询之前。 如果在客户端上计算表达式,则该计算结果将替换为查询中的表达式,然后在服务器上执行查询。 由于查询在数据源上执行,因此数据源配置将替代客户端中指定的行为。 例如,null 值处理和数值精度取决于服务器设置。 在服务器上执行查询期间引发的任何异常将直接传递到客户端。
小窍门
有关表格式查询运算符的便捷摘要,可让你快速识别作员的执行行为,请参阅按执行方式分类标准查询运算符(C#)。
延迟执行查询
在返回值序列的查询中,查询变量本身永远不会保存查询结果,并且只存储查询命令。 查询的执行会被推迟,直到查询变量在foreach或For Each循环中被迭代。 这称为 延迟执行;也就是说,查询执行在构造查询后一段时间发生。 这意味着可以根据需要尽可能频繁地执行查询。 例如,当你有一个数据库正在由其他应用程序更新时,这非常有用。 在应用程序中,可以创建一个查询来检索最新信息并重复执行查询,每次返回更新的信息。
延迟执行使多个查询可以合并或扩展查询。 扩展查询时,会对其进行修改以包含新操作,最终执行将会反映这些更改。 在以下示例中,第一个查询返回所有产品。 第二个查询使用 Where 返回所有大小为“L”的产品,从而对第一个查询进行扩展。
using (AdventureWorksEntities context = new AdventureWorksEntities())
{
IQueryable<Product> productsQuery =
from p in context.Products
select p;
IQueryable<Product> largeProducts = productsQuery.Where(p => p.Size == "L");
Console.WriteLine("Products of size 'L':");
foreach (var product in largeProducts)
{
Console.WriteLine(product.Name);
}
}
Using context As New AdventureWorksEntities()
Dim productsQuery = _
From p In context.Products _
Select p
Dim largeProducts = _
productsQuery.Where(Function(p) p.Size = "L")
Console.WriteLine("Products of size 'L':")
For Each product In largeProducts
Console.WriteLine(product.Name)
Next
End Using
执行查询后,所有后续查询都将使用内存中 LINQ 运算符。 使用 foreach 或 For Each 语句或调用其中一个 LINQ 转换运算符循环访问查询变量将导致立即执行。 这些转换运算符包括: ToList、 ToArray、 ToLookup和 ToDictionary。
立即执行查询
与返回多个值的查询延迟执行相比,返回单个值的查询会立即执行。 单一实例查询的一些示例包括Average、CountFirst和Max。 这些查询立即执行,因为查询必须生成序列才能计算单一实例结果。 还可以强制立即执行。 如果要缓存查询的结果,这非常有用。 若要强制立即执行不生成单一实例值的查询,可以对查询或查询变量调用 ToList 方法、 ToDictionary 方法或 ToArray 方法。 以下示例使用 ToArray 方法立即将序列转换为数组。
using (AdventureWorksEntities context = new AdventureWorksEntities())
{
ObjectSet<Product> products = context.Products;
Product[] prodArray = (
from product in products
orderby product.ListPrice descending
select product).ToArray();
Console.WriteLine("Every price from highest to lowest:");
foreach (Product product in prodArray)
{
Console.WriteLine(product.ListPrice);
}
}
Using context As New AdventureWorksEntities
Dim products As ObjectSet(Of Product) = context.Products
Dim prodArray As Product() = ( _
From product In products _
Order By product.ListPrice Descending _
Select product).ToArray()
Console.WriteLine("The list price from highest to lowest:")
For Each prod As Product In prodArray
Console.WriteLine(prod.ListPrice)
Next
End Using
您还可以通过在查询表达式后立即放置 foreach 或 For Each 循环来强制执行,但通过调用 ToList 或 ToArray ,您可以将所有数据缓存到单个集合对象中。
存储区执行
通常,LINQ to Entities 中的表达式在服务器上计算,不应预期表达式的行为遵循公共语言运行时(CLR)语义,但应遵循数据源的行为。 但是,存在例外情况,例如在客户端上执行表达式时。 这可能会导致意外结果,例如,当服务器和客户端位于不同的时区时。
查询中的某些表达式可能在客户端上执行。 一般情况下,大多数查询执行应在服务器上发生。 除了针对映射到数据源的查询元素执行的方法之外,查询中通常还有一些表达式可以在本地执行。 查询表达式的本地执行将生成可用于查询执行或结果构造的值。
某些操作始终在客户端上执行,例如,闭包中值、子表达式、子查询的绑定以及将对象具体化到查询结果等。 其净效果是在执行期间无法更新这些元素(例如参数值)。 匿名类型可以在数据源上以内联方式构造,但不应假定可以这样做。 还可以在数据源中构造内联分组,但不应在每个实例中假定这一点。 一般情况下,最好不要对服务器上构造的内容做出任何假设。
本部分介绍在客户端本地执行代码的方案。 有关在本地执行哪些类型的表达式的详细信息,请参阅 LINQ to Entities 查询中的表达式。
文本和参数
本地变量(如 orderID 以下示例中的变量)在客户端上求值。
int orderID = 51987;
IQueryable<SalesOrderHeader> salesInfo =
from s in context.SalesOrderHeaders
where s.SalesOrderID == orderID
select s;
Dim orderID As Integer = 51987
Dim salesInfo = _
From s In context.SalesOrderHeaders _
Where s.SalesOrderID = orderID _
Select s
还会在客户端上评估方法参数。
orderID传入MethodParameterExample方法的参数如下例。
public static void MethodParameterExample(int orderID)
{
using (AdventureWorksEntities context = new AdventureWorksEntities())
{
IQueryable<SalesOrderHeader> salesInfo =
from s in context.SalesOrderHeaders
where s.SalesOrderID == orderID
select s;
foreach (SalesOrderHeader sale in salesInfo)
{
Console.WriteLine("OrderID: {0}, Total due: {1}", sale.SalesOrderID, sale.TotalDue);
}
}
}
Function MethodParameterExample(ByVal orderID As Integer)
Using context As New AdventureWorksEntities()
Dim salesInfo = _
From s In context.SalesOrderHeaders _
Where s.SalesOrderID = orderID _
Select s
Console.WriteLine("Sales order info:")
For Each sale As SalesOrderHeader In salesInfo
Console.WriteLine("OrderID: {0}, Total due: {1}", sale.SalesOrderID, sale.TotalDue)
Next
End Using
End Function
在客户端上强制转换文本
在客户端上执行从 null 到 CLR 类型的强制转换:
IQueryable<Contact> query =
from c in context.Contacts
where c.EmailAddress == (string)null
select c;
Dim query = _
From c In context.Contacts _
Where c.EmailAddress = CType(Nothing, String) _
Select c
在客户端上执行到某一类型(如可以为 null 的 Decimal)的强制转换:
var weight = (decimal?)23.77;
IQueryable<Product> query =
from product in context.Products
where product.Weight == weight
select product;
Dim weight = CType(23.77, Decimal?)
Dim query = _
From product In context.Products _
Where product.Weight = weight _
Select product
用于文本的构造函数
可在客户端上执行可映射到概念模型类型的新 CLR 类型:
var weight = new decimal(23.77);
IQueryable<Product> query =
from product in context.Products
where product.Weight == weight
select product;
Dim weight = New Decimal(23.77)
Dim query = _
From product In context.Products _
Where product.Weight = weight _
Select product
新数组也会在客户端上执行。
存储区异常
查询执行过程中遇到的任何存储错误都会传递到客户端,并且不会映射或处理。
存储配置
当查询在存储上执行时,存储配置将替代所有客户端的行为,存储语义适用于所有操作和表达式。 这可能会导致 CLR 和存储执行在一些领域(例如空值比较、GUID 排序、涉及非精确数据类型(如浮点类型或DateTime)的操作精度和准确性以及字符串操作)上的行为差异。 检查查询结果时,请务必记住这一点。
例如,CLR 和 SQL Server 的行为存在一些差异:
SQL Server 对 GUID 的排序方式与 CLR 不同。
处理 SQL Server 上的十进制类型时,结果精度也可能存在差异。 这是因为 SQL Server 小数类型的固定精度要求。 例如,客户端内存中值 0.0、0.0 和 1.0 的平均值 Decimal 为 0.3333333333333333333333333333,但存储区中为 0.3333333(基于 SQL Server 小数类型的默认精度)。
SQL Server 中的某些字符串比较作也与 CLR 中的处理方式不同。 字符串比较行为取决于服务器上的排序规则设置。
函数或方法调用(包含在 LINQ to Entities 查询中)映射到 Entity Framework 中的规范函数,然后转换为 Transact-SQL 并在 SQL Server 数据库上执行。 在某些情况下,这些映射函数表现出的行为可能与基类库中的实现不同。 例如,调用ContainsStartsWith空字符串作为EndsWith参数的方法将在 CLR 中执行时返回
true,但在 SQL Server 中执行时将返回false。 此方法 EndsWith 也可能返回不同的结果,因为对于仅尾随空格不同的两个字符串,SQL Server 认为它们是相等的,而 CLR 认为它们不相等。 以下示例对此进行了说明:
using (AdventureWorksEntities context = new AdventureWorksEntities())
{
IQueryable<string> query = from p in context.Products
where p.Name == "Reflector"
select p.Name;
IEnumerable<bool> q = query.Select(c => c.EndsWith("Reflector "));
Console.WriteLine("LINQ to Entities returns: " + q.First());
Console.WriteLine("CLR returns: " + "Reflector".EndsWith("Reflector "));
}
Using context As New AdventureWorksEntities()
Dim query = _
From p In context.Products _
Where p.Name = "Reflector" _
Select p.Name
Dim q = _
query.Select(Function(c) c.EndsWith("Reflector "))
Console.WriteLine("LINQ to Entities returns: " & q.First())
Console.WriteLine("CLR returns: " & "Reflector".EndsWith("Reflector "))
End Using