在用户创建 LINQ 查询后,该查询将转换为一个命令目录树。命令目录树是与实体框架兼容的查询表示形式。然后,针对数据源执行该命令目录树。在执行查询时,将计算所有查询表达式(即查询的所有组成部分),包括在结果具体化中使用的那些表达式。
执行查询表达式的时间点可能会有所不同。LINQ 查询始终在循环访问查询变量时执行,而不是在创建查询变量时执行。这称为“延迟执行”**。您也可以强制立即执行查询,这对于缓存查询结果很有用。本主题稍后将对此进行介绍。
当执行 LINQ to Entities 查询时,查询中的有些表达式可能在服务器上执行,而有些部分可能在客户端上本地执行。表达式的客户端计算发生于在服务器上执行查询之前。如果在客户端上计算表达式,则该计算的结果将替换查询中的表达式,然后在服务器上执行查询。因为是对数据源执行查询,所以数据源配置将重写客户端中指定的行为。例如,null 值处理和数值精度取决于服务器设置。在查询执行期间在服务器上引发的任何异常都将直接向上传递到客户端。
延迟执行
如果将查询设计为返回一系列值,则查询变量本身仅存储查询命令。如果查询不包含可使查询立即执行的方法,则查询的实际执行将会推迟,直到在 foreach 或 For Each 循环中循环访问查询变量。每次在 foreach 或 For Each 循环中循环访问查询变量时,都会对服务器执行查询。这称为“延迟执行”。通过延迟执行,多个查询可以组合在一起,查询也可以得到扩展。这种情况下,将修改查询以包括新操作,而最终执行将反映这些更改。查询变量本身从不保存查询结果。这意味着您可以以任意频率执行查询。例如,您可能有一个正由单独的应用程序不断进行更新的数据库。您可以在应用程序中创建一个检索最新数据的查询,您可以按某一时间间隔重复执行该查询,从而在每次执行时都检索到不同的结果。
LINQ to Entities 查询会转换为实体框架中的命令目录树,并在循环访问结果时对数据源执行这些查询。此时,转换失败会导致向客户端引发异常。
立即执行
与返回一系列值的延迟执行查询相反,返回单一实例值的查询将立即执行。Average、Count、First 和 Max 是单一实例查询的一些示例。这些查询立即执行,因为查询必须生成序列才能计算单一实例结果。您也可以强制立即执行。如果要缓存查询结果,这么做十分有用。若要强制立即执行不生成单一实例值的查询,可以对查询或查询变量调用 ToList、ToDictionary 或 ToArray 方法。下面的示例使用 ToArray 方法立即将序列转换为数组。
Using AWEntities As New AdventureWorksEntities
    Dim products As ObjectQuery(Of Product) = AWEntities.Product
    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
using (AdventureWorksEntities AWEntities = new AdventureWorksEntities())
{
    ObjectQuery<Product> products = AWEntities.Product;
    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);
    }
}
通过在查询表达式之后紧随 foreach 或 For Each 循环,也可以强制执行,而通过调用 ToList 或 ToArray,可以缓存单个集合对象中的所有数据。
存储区执行
通常,LINQ to Entities 中的表达式将在服务器上计算,表达式的行为不会遵循公共语言运行库 (CLR) 语义,而是遵循数据源的语义。但存在一些例外情况,例如,在客户端上执行表达式时。这可能会导致意外结果,例如,服务器和客户端位于不同的时区时。
查询中的某些表达式可能在客户端上执行。通常,大多数查询表达式应在服务器上执行。除了对映射到数据源的查询元素所执行的方法之外,查询中通常还有一些表达式可在本地执行。本地执行查询表达式会生成一个可在查询执行或结果构造中使用的值。
某些操作始终在客户端上执行,例如,闭包中值、子表达式、子查询的绑定以及将对象具体化到查询结果等。这种做法的最终结果是,这些元素(如参数值)不能在执行期间更新。匿名类型可以在数据源上以内联方式构造,但不应假定可以这样做。也可以在数据源中构造内联分组,但不应假定在所有情况下都可以这样做。总之,最好不要对于哪些内容是在服务器上构造的进行假定。
本节介绍在客户端本地执行代码的方案。有关哪些类型的表达式可在本地执行的更多信息,请参见 LINQ to Entities 查询中的表达式。
文本和参数
本地变量(如下例中的 orderID 变量)是在客户端上计算的。
Dim sales As ObjectQuery(Of SalesOrderHeader) = AWEntities.SalesOrderHeader
Dim orderID As Integer = 51987
Dim salesInfo = _
    From s In sales _
    Where s.SalesOrderID = orderID _
    Select s
ObjectQuery<SalesOrderHeader> sales = AWEntities.SalesOrderHeader;
int orderID = 51987;
IQueryable<SalesOrderHeader> salesInfo =
    from s in sales
    where s.SalesOrderID == orderID
    select s;
方法参数也是在客户端上计算的。传入以下 MethodParameterExample 方法的 orderID 参数就是一个示例。
Function MethodParameterExample(ByVal orderID As Integer)
    Using AWEntities As New AdventureWorksEntities()
        Dim sales As ObjectQuery(Of SalesOrderHeader) = AWEntities.SalesOrderHeader
        Dim salesInfo = _
            From s In sales _
            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
public static void MethodParameterExample(int orderID)
{
    using (AdventureWorksEntities AWEntities = new AdventureWorksEntities())
    {
        
        ObjectQuery<SalesOrderHeader> sales = AWEntities.SalesOrderHeader;
                        
        IQueryable<SalesOrderHeader> salesInfo =
            from s in sales
            where s.SalesOrderID == orderID
            select s;                
        foreach (SalesOrderHeader sale in salesInfo)
        {
            Console.WriteLine("OrderID: {0}, Total due: {1}", sale.SalesOrderID, sale.TotalDue);
        }
    }
}
在客户端上强制转换文本
在客户端上执行从 null 到 CLR 类型的强制转换:
Dim contacts As ObjectQuery(Of Contact) = AWEntities.Contact
Dim query = _
    From c In contacts _
    Where c.EmailAddress = CType(Nothing, String) _
    Select c
ObjectQuery<Contact> contacts = AWEntities.Contact;
IQueryable<Contact> query =
    from c in contacts
    where c.EmailAddress == (string)null
    select c;
在客户端上执行到某一类型(如可以为 null 的 Decimal)的强制转换:
Dim products As ObjectQuery(Of Product) = AWEntities.Product
Dim query = _
    From product In products _
        Where product.Weight = CType(23.77, Decimal?) _
        Select product
ObjectQuery<Product> products = AWEntities.Product;
IQueryable<Product> query =
    from product in products
    where product.Weight == (decimal?)23.77
    select product;
用于文本的构造函数
可映射到 EDM 类型的新 CLR 类型是在客户端上执行的:
Dim products As ObjectQuery(Of Product) = AWEntities.Product
Dim query = _
    From product In products _
    Where product.Weight = New Decimal(23.77) _
    Select product
ObjectQuery<Product> products = AWEntities.Product;
IQueryable<Product> query =
    from product in products
    where product.Weight == new decimal(23.77)
    select product;
新数组也是在客户端上执行的。
存储区异常
执行查询时出现的任何存储区错误都会向上传递到客户端,不会进行映射或处理。
存储区配置
在存储区上执行查询时,存储区配置将覆盖所有客户端行为,并用存储区语义表示所有运算和表达式。这可能导致 CLR 与存储区执行之间的行为在某些方面存在差异,例如 null 比较、GUID 排序、涉及非精确数据类型(如浮点类型或 DateTime)运算的准确性以及字符串运算等。检查查询结果时,请务必注意这一点。
例如,下面列出了 CLR 与 SQL Server 之间的一些行为差异:
- 在 GUID 排序方式上,SQL Server 不同于 CLR。 
- 在处理 SQL Server 十进制类型时,这两者在结果精度方面也存在差异。这是因为 SQL Server 十进制类型具有固定精度要求。例如,在客户端内存中,Decimal 值 0.0、0.0 和 1.0 的平均值为 0.3333333333333333333333333333,而在存储区中则为 0.333333(基于 SQL Server 的十进制类型的默认精度)。 
- 在处理一些字符串比较运算的方式上,SQL Server 也不同于 CLR。在服务器上,字符串比较行为取决于排序规则设置。 
- 函数或方法调用如果包含在 LINQ to Entities 查询中,则会先映射到实体框架中的规范函数,然后再转换为 Transact-SQL 并针对 SQL Server 数据库执行。在有些情况下,这些映射函数的行为可能不同于基类库中的实现。例如,将一个空字符串用作参数调用 Contains、StartsWith 和 EndsWith 方法时,如果在 CLR 中执行,会返回 true;如果在 SQL Server 中执行,则会返回 false。如果两个字符串只有尾随空格不同,SQL Server 认为它们相等,而 CLR 认为它们不相等,因此 EndsWith 方法也会返回不同的结果。下面的示例对此进行演示: 
Using AWEntities As New AdventureWorksEntities()
    Dim products As ObjectQuery(Of Product) = AWEntities.Product
    Dim query = _
        From p In 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
using (AdventureWorksEntities AWEntities = new AdventureWorksEntities())
{
    ObjectQuery<Product> products = AWEntities.Product;
    IQueryable<string> query = from p in 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 "));
}