查询执行

用户创建 LINQ 查询后,它将转换为命令树。 命令树是与 Entity Framework 兼容的查询的表示形式。 然后,对数据源执行命令树。 在查询执行时,将计算所有查询表达式(即查询的所有组件),包括用于结果具体化的表达式。

执行查询表达式时可能会有所不同。 LINQ 查询总是在查询变量被遍历时执行,而不是在查询变量创建时执行。 这称为 延迟执行。 还可以强制查询立即执行,这对于缓存查询结果很有用。 本主题稍后将对此进行介绍。

执行 LINQ to Entities 查询时,查询中的某些表达式可能在服务器上执行,某些部分可能在客户端本地执行。 表达式的客户端计算发生于在服务器上执行查询之前。 如果在客户端上计算表达式,则该计算结果将替换为查询中的表达式,然后在服务器上执行查询。 由于查询在数据源上执行,因此数据源配置将替代客户端中指定的行为。 例如,null 值处理和数值精度取决于服务器设置。 在服务器上执行查询期间引发的任何异常将直接传递到客户端。

小窍门

有关表格式查询运算符的便捷摘要,可让你快速识别作员的执行行为,请参阅按执行方式分类标准查询运算符(C#)。

延迟执行查询

在返回值序列的查询中,查询变量本身永远不会保存查询结果,并且只存储查询命令。 查询的执行会被推迟,直到查询变量在foreachFor 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 运算符。 使用 foreachFor Each 语句或调用其中一个 LINQ 转换运算符循环访问查询变量将导致立即执行。 这些转换运算符包括: ToListToArrayToLookupToDictionary

立即执行查询

与返回多个值的查询延迟执行相比,返回单个值的查询会立即执行。 单一实例查询的一些示例包括AverageCountFirstMax。 这些查询立即执行,因为查询必须生成序列才能计算单一实例结果。 还可以强制立即执行。 如果要缓存查询的结果,这非常有用。 若要强制立即执行不生成单一实例值的查询,可以对查询或查询变量调用 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

您还可以通过在查询表达式后立即放置 foreachFor Each 循环来强制执行,但通过调用 ToListToArray ,您可以将所有数据缓存到单个集合对象中。

存储区执行

通常,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