【问题标题】:Different SQL produced from Where(l => l.Side == 'A') vs Where(l => l.Side.Equals('A')从 Where(l => l.Side == 'A') 与 Where(l => l.Side.Equals('A') 产生的不同 SQL
【发布时间】:2016-03-07 21:33:00
【问题描述】:

我一直在试验 LinqPad 中的查询。我们有一个表Lot,其中有一列Side char(1)。当我编写一个 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。

使用int、smallint或varchar列生成的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 版本中,它作为char 传递。

The dbml and table creation script are in this gist.

【问题讨论】:

  • 你能发布 DBML 映射吗?
  • @usr 这是通过 LINQPad,我不知道如何检索映射。

标签: c# linq-to-sql linqpad


【解决方案1】:

关于这个有一些documentation:

SQL Server 中的不匹配: 固定长度的字符类型。 Transact-SQL 区分 Unicode 和非 Unicode 类别,并在每个类别中具有三种不同的类型:固定长度的 nchar/char、可变长度的 nvarchar/varchar 和更大的 ntext/text。固定长度的字符类型可以映射到 CLR System.Char 类型以检索字符,但它们在转换和行为上并不真正对应相同的类型。

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

看来函数出现的必要前提是SqlUnary类型为Convert的语法树节点:

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

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

Equals 在源代码中的多个位置被翻译。这是其中之一:

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


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

【讨论】:

  • l.Side.Equals(1.2m) 在生成任何 SQL 之前抛出 InvalidCastException (无法从类型“System.Decimal”转换为类型“System.Char”。) - 至少,任何可见的 sql 无需挖掘进入内部。
  • 我可能应该补充一点,我正在使用 LINQPad 来查看创建的 SQL,如果它有什么不同的话。我假设 LINQPad 会使用系统 L2S 库,但我意识到这个假设可能有缺陷。
  • LinqPad 可能使用未修改的二进制文件。您可以通过 L2S 的运行时元数据系统查询映射。有 MSDN 文档。但是如果你创建一个 VS 项目来运行你的测试会更好。鉴于 LinqPad 可能使用各种奇怪的映射和侵入性技术,现在我不知道该怎么做。
  • 请参阅问题的编辑 3。 VS 项目给出相同的结果。
  • 您只是想消除这种情况还是想了解为什么会发生这种情况?首先要尝试将其映射到字符串。这不会导致任何性能损失,但它可能会解决此 L2S 错误/缺陷。
猜你喜欢
  • 2021-09-21
  • 2020-01-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多