【问题标题】:LINQ to SQL join generates SQL which joins on IS NULLLINQ to SQL 连接生成在 IS NULL 上连接的 SQL
【发布时间】:2016-01-08 12:16:47
【问题描述】:

我不擅长Linq表达式,今天我遇到了一个奇怪的问题,如下inner join语句,

var orders = (from q in dao.CurrentDBContext.New_OrderForm 
              join d in dao.CurrentDBContext.New_OrderGoodsDetail on q.billNum equals d.billNum
              select new
             {    
                q.billNum,
                q.orderSource,
                q.sourceOddNum    
                d.PPT    
             }

当我跟踪 linq 语句时,我很困惑 Entity Framework 会将 linq 语句转换为下面的 sql 语句

SELECT 
[Extent1].[billNum] AS [billNum], 
[Extent1].[orderSource] AS [orderSource], 
[Extent1].[sourceOddNum] AS [sourceOddNum], 
[Extent2].[PPT] AS [PPT]
FROM [dbo].[New_OrderForm] AS [Extent1]
INNER JOIN [dbo].[New_OrderGoodsDetail] AS [Extent2] 
           ON ([Extent1].[billNum] = [Extent2].[billNum]) OR 
              (([Extent1].[billNum] IS NULL) AND ([Extent2].[billNum] IS NULL))

你知道为什么下面的 SQL 段会自动追加吗?

OR (([Extent1].[billNum] IS NULL) AND ([Extent2].[billNum] IS NULL)"

我不希望上面的内容会自动附加,因为它确实降低了 SQL 性能。有什么建议吗?

【问题讨论】:

  • q.billNumd.billNum类型是什么?
  • 两种数据类型相同,字符串类型
  • 您的列可以为空吗?也许q.billNum equals d.billNum 中的等号假设您也希望NULL == NULL 匹配?
  • 是的,该列可以为空。
  • 可能你没有按要求配置它们。你真的需要它们可以为空吗?因为这就是 EF 附加您所要求的标准的原因。

标签: c# .net linq join


【解决方案1】:

如果您无法将 billNum 列更改为不可为空,您可以执行以下操作。

首先,设置@Giorgi提到的选项

class CurrentDBContext
{
    public CurrentDBContext()
    {
        Configuration.UseDatabaseNullSemantics = true;
        // ...
    }
}

然后将 LINQ 查询改为不使用join,而是像这样简单的where

var orders = (from q in dao.CurrentDBContext.New_OrderForm 
              from d in dao.CurrentDBContext.New_OrderGoodsDetail
              where q.billNum == d.billNum
              select ...

结果将是与您显示的完全相同的 SQL 查询(with JOIN!)没有OR 部分。

【讨论】:

  • 只是为了强调连接条件需要移动到伊凡所说的地方。我有 UseDatabaseNullSemantics = true,但仍然使用 on join 语法,并没有得到预期的结果
【解决方案2】:

似乎 Linq 转换 q.billNum equals d.billNum 的方式是它还包含一个有效匹配,以防 q.billNum 和 d.billNum 都是 NULL(在 SQL 中,NULL 永远不等于 NULL,因此查询中的 OR )。

让两个字段都不可为空是最好的解决方案,前提是这两个字段都不能为空。

如果不是这种情况,您还可以尝试在 Linq 语句中添加 where 子句来指定 q.billNum 和 d.billNum 不能为 NULL。如果运气好的话,Linq 将认识到不可能为 null 的值。

注意:如果您使用 Oracle,您应该检查空字符串以及 NULL(空字符串等同于 NULL)。空字符串应该可以作为 SQL Server 中的有效值。

由于上述没有帮助,您可以尝试自己编写查询。如果我没记错的话,这将是以下几行(假设您的示例代码中 varList<Order> - 您的查询结果应该与您使用的类匹配):

StringBuilder query = new StringBuilder();
query.AppendLine("SELECT [Extent1].[billNum] AS [billNum],");
query.AppendLine("       [Extent1].[orderSource] AS [orderSource],");
query.AppendLine("       [Extent1].[sourceOddNum] AS [sourceOddNum],");
query.AppendLine("       [Extent2].[PPT] AS [PPT]");
query.AppendLine("FROM [dbo].[New_OrderForm] AS [Extent1]");
query.AppendLine("INNER JOIN [dbo].[New_OrderGoodsDetail] AS [Extent2] ON [Extent1].[billNum] = [Extent2].[billNum]");

List<Order> orders = DbContext.Database.SqlQuery<Order>(query.ToString()).ToList();

我过去曾使用过类似的解决方法来解决性能问题。

【讨论】:

  • 添加 where 子句不会删除“额外的”OR 条件(这将需要 EF 对查询表达式执行“全局”优化)。但是,SQL Server 执行计划可能会受益于知道billNum 永远不会是NULL
  • 您好 Sam,感谢您的详细回复,由于系统在线,现在可能不可行,客户不允许我将该字段设为不可为空,我已尝试此 'var 订单= (from q in dao.CurrentDBContext.New_OrderForm.Where(p=>p.billNum!=null) join d in dao.CurrentDBContext.New_OrderGoodsDetail.Where(p=>p.billNum!=null) on q.billNum 等于 d .billNum',
  • 并尝试了 ' 或 'from dao.CurrentDBContext.New_OrderForm from d in dao.CurrentDBContext.New_OrderGoodsDetail .Where(p=>p.billNum==q.billNum&& p.billNum!= null&& q.billNum!=null)''
  • 很可惜,这两种方法都没有用。:(
  • 感谢 Sam,这也是一个很好的解决方法,但我必须从 linq 代码切换 sql 语句,以便需要更改大量 linq 代码,因为在业务逻辑代码中附加了很多动态条件,正如您提到的,最好的解决方法是使这两个字段不可为空,我需要说服客户更改它。 :):)
【解决方案3】:

如果您使用的是 EF6,请尝试设置

context.Configuration.UseDatabaseNullSemantics = true;

并且它不会为这些列生成 NULL 检查。

According to documentation

例如 (operand1 ==operand2) 将被翻译为: (operand1 =operand2) if UseDatabaseNullSemantics 分别为真 (((operand1 =operand2) AND (NOT (operand1 IS NULL OR operand2 IS NULL))) OR ( (operand1 IS NULL) AND (operand2 IS NULL))) 如果 UseDatabaseNullSemantics 为假。

【讨论】:

  • 实际测试表明这样并不能解决问题(生成相同的SQL)。与问题中的内容相比,文档中的 SQL 也有所不同。我不确定UseDatabaseNullSemantics 到底做了什么。
  • 对了,我测试了一下,开关好像没用。:(
  • 这对我来说非常有效。将该标志设置为 true 会删除在可空字符串比较中创建的 SQL OR。谢谢@Giorgi。
  • 刚刚我安装了 EF 6.1.3,但它仍然无法正常工作。 :(
  • 抱歉,我在带有 == 操作数的 where 子句中使用它(根据文档中的示例)。在连接上使用 equals 关键字进行测试后,它没有任何效果。令人沮丧!
【解决方案4】:

继@Giorgi 的回答之后,UseDatabaseNullSemantics 标志将不适用于 equals 关键字 - 只有 == 操作数。因此,为了解决这个问题并确保在 billNum 上的连接不是 OR 子句的一部分,这种方法应该有效(与 UseDatabaseNullSemantics 标志一起使用):

var orders = (from q in dao.CurrentDBContext.New_OrderForm 
          from d in dao.CurrentDBContext.New_OrderGoodsDetail 
          where q.billNum == d.billNum
          select new
         {    
            q.billNum,
            q.orderSource,
            q.sourceOddNum    
            d.PPT    
         }

这将生成没有ORJOIN

【讨论】:

  • 第二行,必须是“from ...”而不是“join ...”。因为 join 将期望等于
  • 谢谢 - 好地方,因为答案在很大程度上被忽略了!我已经进行了相应的编辑。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-10-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-24
  • 2014-04-08
  • 2014-08-25
相关资源
最近更新 更多