标准查询运算符转换

LINQ to SQL 将标准查询运算符转换为 SQL 命令。 数据库的查询处理器确定 SQL 转换的执行语义。

标准查询运算符针对 序列定义。 序列是 有序的 ,依赖于序列的每个元素的引用标识。 有关详细信息,请参阅标准查询运算符概述(C#)标准查询运算符概述(Visual Basic)。

SQL 主要处理 无序值集。 排序通常是显式声明的后处理作,该作应用于查询的最终结果,而不是中间结果。 标识由值定义。 因此,将 SQL 查询理解为处理多重集(包)而非集合

以下段落描述了标准查询运算符及其 SQL 翻译对于 LINQ to SQL 的 SQL Server 提供程序之间的差异。

操作员支持

Concat

该方法 Concat 为有序的多集定义,其中接收方的顺序和参数的顺序相同。 Concat 的作用等效于对多重集执行 UNION ALL,紧接着再执行常见排序。

最后一步是在生成结果之前在 SQL 中排序。 Concat 不保留其参数的顺序。 若要确保适当的排序,必须对Concat的结果显式排序。

Intersect、Except、Union

IntersectExcept 方法仅对集合而言是定义完善的。 多集的语义未定义。

多集方法Union被定义为多集的无序连接(这实际上类似于 SQL 中的 UNION ALL 子句结果)。

Take、Skip

TakeSkip 方法只有针对 有序集才定义得很好。 未排序集或多集的语义未定义。

注释

TakeSkip 在对 SQL Server 2000 进行查询时存在一些限制。 有关详细信息,请参阅 故障排除中的“跳过和排除 SQL Server 2000 中的异常”条目。

由于 SQL 中的排序限制,LINQ to SQL 会尝试将这些方法的参数排序移动到方法的结果。 例如,请考虑以下 LINQ to SQL 查询:

var custQuery =
    (from cust in db.Customers
    where cust.City == "London"
    orderby cust.CustomerID
    select cust).Skip(1).Take(1);
Dim custQuery = _
    From cust In db.Customers _
    Where cust.City = "London" _
    Order By cust.CustomerID _
    Select cust Skip 1 Take 1

此代码生成的 SQL 将排序移动到末尾,如下所示:

SELECT TOP 1 [t0].[CustomerID], [t0].[CompanyName],
FROM [Customers] AS [t0]
WHERE (NOT (EXISTS(
    SELECT NULL AS [EMPTY]
    FROM (
        SELECT TOP 1 [t1].[CustomerID]
        FROM [Customers] AS [t1]
        WHERE [t1].[City] = @p0
        ORDER BY [t1].[CustomerID]
        ) AS [t2]
    WHERE [t0].[CustomerID] = [t2].[CustomerID]
    ))) AND ([t0].[City] = @p1)
ORDER BY [t0].[CustomerID]

很明显,当 TakeSkip 链接在一起时,所有指定的排序都必须一致。 否则,结果未定义。

对于非负的、基于标准查询运算符规范的整型常量参数,TakeSkip 都是定义完善的。

不进行转换的运算符

LINQ to SQL 不会转换以下方法。 最常见的原因是无序多集和序列之间的差异。

运营商 理由
TakeWhileSkipWhile SQL 查询是针对多集进行操作,而不是针对序列。 ORDER BY 必须是应用于结果的最后一个子句。 因此,不存在适用于这两个方法的通用转换。
Reverse 此方法的翻译对于有序集是可能的,但目前无法由 LINQ to SQL 翻译。
LastLastOrDefault 这些方法的翻译对于有序集是可能的,但目前不是由 LINQ to SQL 翻译的。
ElementAtElementAtOrDefault SQL 查询基于多集执行,而不是基于可索引的序列。
DefaultIfEmpty(带默认参数的重载) 通常,不能为任意元组指定默认值。 在某些情况下,可以通过外部联接为元组指定 Null 值。

表达式转换

Null 语义

LINQ to SQL 不会对 SQL 施加 null 比较语义。 比较运算符在语法上转换为其 SQL 等效项。 因此,语义反映了由服务器或连接设置定义的 SQL 语义。 例如,在默认 SQL Server 设置下,两个 null 值被视为不相等,但可以更改设置以更改语义。 LINQ to SQL 在转换查询时不考虑服务器设置。

与文本 null 的比较将转换为相应的 SQL 版本(is nullis not null)。

排序规则中的值 null 由 SQL Server 定义。 LINQ to SQL 不会更改排序规则。

聚合

对于空序列或仅包含 null 的序列,标准查询运算符聚合方法 Sum 的计算结果为零。 在 LINQ to SQL 中,SQL 的语义保持不变,而 Sum 的计算结果为 null,而不是零,对于空序列或仅包含 null 的序列。

中间结果的 SQL 限制适用于 LINQ to SQL 中的聚合。 Sum 32 位整数数量不是使用 64 位结果计算的。 即使标准查询运算符的实现不会导致对应的内存序列溢出,Sum 的 LINQ to SQL 转换仍有可能发生溢出。

同样,使用经 LINQ to SQL 转换后的 Average 计算整数值时,所得结果的数据类型为 integer,而非 double

实体自变量

LINQ to SQL 允许在方法GroupByOrderBy中使用实体类型。 在这些运算符的转换中,使用类型的参数被视为等效于指定该类型的所有成员。 例如,以下代码等效:

db.Customers.GroupBy(c => c);
db.Customers.GroupBy(c => new { c.CustomerID, c.ContactName });
db.Customers.GroupBy(Function(c) c)
db.Customers.GroupBy(Function(c) New With {c.CustomerID, _
    c.ContactName})

可相等/可比较自变量

在实现以下方法时需要参数相等性:

LINQ to SQL 支持 平面 参数的相等性和比较,但不支持包含序列的参数。 平参数是一种能映射到 SQL 行的类型。 可以静态确定不包含序列的一个或多个实体类型的投影被视为平面参数。

下面是平面参数的示例:

db.Customers.Select(c => c);
db.Customers.Select(c => new { c.CustomerID, c.City });
db.Orders.Select(o => new { o.OrderID, o.Customer.City });
db.Orders.Select(o => new { o.OrderID, o.Customer });	
db.Customers.Select(Function(c) c)
db.Customers.Select(Function(c) New With {c.CustomerID, c.City})
db.Orders.Select(Function(o) New With {o.OrderID, o.Customer.City})
db.Orders.Select(Function(o) New With {o.OrderID, o.Customer})

以下是非平面(分层)自变量的一些示例:

// In the following line, c.Orders is a sequence.
db.Customers.Select(c => new { c.CustomerID, c.Orders });
// In the following line, the result has a sequence.
db.Customers.GroupBy(c => c.City);
' In the following line, c.Orders is a sequence.
db.Customers.Select(Function(c) New With {c.CustomerID, c.Orders})
' In the following line, the result has a sequence.
db.Customers.GroupBy(Function(c) c.City)

Visual Basic 函数翻译

Visual Basic 编译器使用的以下帮助程序函数将转换为相应的 SQL 运算符和函数:

  • CompareString

  • DateTime.Compare

  • Decimal.Compare

  • IIf (in Microsoft.VisualBasic.Interaction)

转换方法:

  • ToBoolean
  • ToSByte
  • ToByte
  • ToChar
  • ToCharArrayRankOne
  • ToDate
  • ToDecimal
  • ToDouble
  • ToInteger
  • ToUInteger
  • ToLong
  • ToULong
  • ToShort
  • ToUShort
  • ToSingle
  • ToString

继承支持

继承映射限制

有关详细信息,请参阅 “如何:映射继承层次结构”。

查询中的继承

仅支持在投影中使用 C# 强制转换。 在其他地方使用的强制转换不会进行转换且会被忽略。 除了 SQL 函数名称之外,SQL 实际上只执行公共语言运行时 (CLR) Convert的等效项。 也就是说,SQL 可以将一种类型的值更改为另一种类型。 CLR 强制转换不存在等效项,这是因为不存在这样的概念:重新解释与另一类型的位相同的位。 正因如此,C# 强制转换只能在本地使用。 它不是通过远程控制的。

运算符 isas,以及方法 GetType 不限于 Select 运算符。 还可以在其他查询运算符中使用它们。

SQL Server 2008 支持

从 .NET Framework 3.5 SP1 开始,LINQ to SQL 支持映射到 SQL Server 2008 中引入的新日期和时间类型。 但是,对于您可以在操作映射到这些新类型的值时使用的 LINQ to SQL 查询运算符有一些限制。

不支持的查询运算符

映射到新的 SQL Server 日期和时间类型的值不支持以下查询运算符:DATETIME2DATE、和TIMEDATETIMEOFFSET

  • Aggregate

  • Average

  • LastOrDefault

  • OfType

  • Sum

有关映射到这些 SQL Server 日期和时间类型的详细信息,请参阅 SQL-CLR 类型映射

SQL Server 2005 支持

LINQ to SQL 不支持以下 SQL Server 2005 功能:

  • 为 SQL CLR 编写的存储过程。

  • 用户定义的类型。

  • XML 查询功能。

SQL Server 2000 支持

以下 SQL Server 2000 限制(与 Microsoft SQL Server 2005 相比)会影响 LINQ to SQL 支持。

Cross Apply 和 Outer Apply 运算符

SQL Server 2000 中不提供这些运算符。 LINQ to SQL 会尝试一系列重写,以将其替换为适当的联接。

Cross ApplyOuter Apply 是为关系导航生成的。 可以进行这种重写的查询集定义不完善。 因此,SQL Server 2000 支持的最小查询集是不涉及关系导航的集。

text / ntext

数据类型text / ntext不能用于针对varchar(max) / nvarchar(max)的某些查询操作,而这些查询操作是由 Microsoft SQL Server 2005 支持的。

此限制没有解决方法。 具体而言,不能对包含映射到Distinct()text列的成员的任何结果使用ntext

由嵌套查询触发的行为

SQL Server 2000(通过 SP4)绑定器具有由嵌套查询触发的一些特殊性。 无法很好地定义触发这些特殊性的 SQL 查询集。 因此,无法定义可能导致 SQL Server 异常的 LINQ to SQL 查询集。

Skip 和 Take 运算符

TakeSkip 在对 SQL Server 2000 进行查询时存在一些限制。 有关详细信息,请参阅 故障排除中的“跳过和排除 SQL Server 2000 中的异常”条目。

对象具体化

物化过程从一个或多个 SQL 查询返回的行中生成 CLR 对象。

  • 以下调用作为具体化过程的一部分在本地执行

    • 构造函数

    • 投影中的 ToString 方法

    • 投影中的类型强制转换

  • 接在 AsEnumerable 方法之后的方法在本地执行。 此方法不会导致立即执行。

  • 可以使用 a struct 作为查询结果的返回类型或结果类型的成员。 实体需要变成类。 匿名类型具体化为类实例,但命名结构(非实体)可用于投影。

  • 查询结果的返回类型的成员可以是类型 IQueryable<T>。 它具体化为本地集合。

  • 以下方法导致立即具体化应用这些方法的序列

另请参阅