不同的 SQL 从 Where(l => l.Side == 'A') 与 Where(l => l.Side.Equals('A')

Different SQL produced from Where(l => l.Side == 'A') vs Where(l => l.Side.Equals('A')

我一直在 LinqPad 中试验查询。我们有一个包含 Side char(1) 列的 table Lot。当我写一个 linq to sql 查询 Lots.Where(l => l.Side == 'A') 时,它会产生以下 SQL

-- Region Parameters
DECLARE @p0 Int = 65
-- EndRegion
SELECT ..., [t0].[Side], ...
FROM [Lot] AS [t0]
WHERE UNICODE([t0].[Side]) = @p0

但是,使用 Lots.Where(l => l.Side.Equals('A')),它会产生

-- Region Parameters
DECLARE @p0 Char(1) = 'A'
-- EndRegion
SELECT ..., [t0].[Side], ...
FROM [Lot] AS [t0]
WHERE [t0].[Side] = @p0

经过(尽管是幼稚的)检查,后者会稍微快一些,因为它不需要调用 UNICODE.

使用 intsmallintvarchar 列,生成的 SQL 与 ==.Equals 之间没有区别,为什么char(1)和对应的C#类型char不同?

有什么方法可以预测给定的列类型是否会通过两种形式的相等性检查产生不同的 SQL?

编辑:

我检查了 MS SQL 支持的每种类型,只有 char(1)nchar(1) 显示了这种行为。两者在 LinqToSql 中都由 System.Char 类型表示。如果这是一个深思熟虑的决定,那么我会期望在 binary(1) 上有相同的行为,它可以用 System.Byte 表示(但 System.Linq.Binary 的长度为 1 .

编辑 2:如果相关,我正在使用 LINQPad 查看创建的 SQL。我假设 Linqpad 会使用系统的 LinqToSQL,但今天我意识到这个假设可能有缺陷。

编辑 3:我 运行 一个快速的 VS 项目来测试系统 LinqToSQL,我们得到相同的结果:

代码:

static void Main(string[] args)
{
    var db = new DataClasses1DataContext {Log = Console.Out};
    Console.Out.WriteLine("l.Side == 'A'");
    Console.Out.WriteLine("=============");
    Console.Out.WriteLine();
    foreach (Lot ll in db.Lots.Where(l => l.Side == 'A'))
    {
        break;
    }
    Console.Out.WriteLine();
    Console.Out.WriteLine("---------------------------------------");
    Console.Out.WriteLine();

    Console.Out.WriteLine("l.Side.Equals('A')");
    Console.Out.WriteLine("==================");
    Console.Out.WriteLine();
    foreach (Lot ll in db.Lots.Where(l => l.Side.Equals('A')))
    {
        break;
    }
    Console.In.Read();
}

输出:

l.Side == 'A'
=============

SELECT ..., [t0].[Side], ...
FROM [dbo].[Lot] AS [t0]
WHERE UNICODE([t0].[Side]) = @p0
-- @p0: Input Int (Size = -1; Prec = 0; Scale = 0) [65]
-- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 4.6.1532.0


---------------------------------------

l.Side.Equals('A')
==================

SELECT ..., [t0].[Side], ...
FROM [dbo].[Lot] AS [t0]
WHERE [t0].[Side] = @p0
-- @p0: Input Char (Size = 1; Prec = 0; Scale = 0) [A]
-- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 4.6.1532.0

有趣的是,在 == 'A' 版本中,参数作为 int 传递,而在 .Equals 版本中,它作为 [=30= 传递].

The dbml and table creation script are in this gist.

关于这个有一些documentation

Mismatches in SQL Server: Fixed length character types. Transact-SQL distinguishes between Unicode and non-Unicode categories and has three distinct types in each category: fixed length nchar/char, variable length nvarchar/varchar, and larger-sized ntext/text. The fixed length character types could be mapped to the CLR System.Char type for retrieving characters, but they do not really correspond to the same type in conversions and behavior.

而L2S源码只有一处使用了字符串字面量"UNICODE":

函数显示的必要先决条件似乎是类型为 Convert:

SqlUnary 语法树节点

不知道你是怎么满足IsNumeric条件的。我认为你那里有类型不匹配。该列是否真的映射为 System.Char?

Equals 调用可能不会触发此代码路径。这可能是一个 L2S 错误。

Equals在源代码中多处翻译。这是其中之一:

看起来这绕过了任何参数转换。它不关心参数是什么。这可能会因各种查询而失败(因此它可能是一个错误)。我想知道如果你写 l.Side.Equals(1.2m) 会发生什么。我想这会直译为 SQL.


我现在已经复制了它。将列映射到 string。这修复了生成的 SQL。执行计划显示可以使用正在生成的 SQL 进行索引查找。