如何在实体框架中查询空值?

112

我想执行这样的查询

   var result = from entry in table
                     where entry.something == null
                     select entry;

并且生成一个 IS NULL

编辑: 在看到前两个答案后,我感到有必要澄清一点,我正在使用Entity Framework而不是Linq to SQL。在EF中,object.Equals()方法似乎不起作用。

第二次编辑: 上面的查询按预期工作。它正确地生成了IS NULL。然而,我的生产代码是

value = null;
var result = from entry in table
                         where entry.something == value
                         select entry;

生成的SQL是something = @p; @p = NULL。看起来EF能够正确地翻译常量表达式,但如果涉及变量,则像普通的比较一样处理。实际上很有道理。我将关闭此问题。


18
我认为这并没有太多意义……连接器应该有一点智能,而不是让我们来完成它的工作:将正确的C#查询正确翻译成SQL。这会导致不符合预期的行为。 - Julien N
6
我和朱利安在一起,这是EF方面的失败。 - Mr Bell
1
这是标准的失败,现在只会变得更糟,因为从SQL Server 2016开始,与null进行比较永久地导致未定义,并且ANSI NULLs永久设置为开启。Null可能表示未知值,但"null"本身不是未知值。将null值与null值进行比较应该绝对返回true,但不幸的是,标准也背离了常识和布尔逻辑。 - Triynko
14个回答

126

Linq-to-SQL的解决方法:

var result = from entry in table
             where entry.something.Equals(value)
             select entry;

Linq-to-Entities(痛苦!)的解决方法:

var result = from entry in table
             where (value == null ? entry.something == null : entry.something == value)
             select entry;

这是一个非常讨厌的错误,我也遇到过好几次。如果你也受到了这个 bug 的影响,请访问 UserVoice 上的 bug 报告,让微软知道这个 bug 也影响了你。


编辑:EF 4.5 中已修复此问题!感谢大家的支持和投票!

为了向后兼容,这将是一项选择性功能 - 您需要手动启用设置才能使 entry == value 正常工作。目前还没有关于这个设置的详细信息,敬请期待!


编辑 2:根据 EF 团队发布的这篇文章这个问题已经在 EF6 中修复!太棒了!

我们更改了 EF6 的默认行为以补偿三值逻辑。

这意味着依赖旧行为的现有代码(null != null,但仅当与变量比较时))将需要更改以不再依赖该行为,或者将UseCSharpNullComparisonBehavior设置为 false 以使用旧的错误行为。


6
我已经点赞了这个错误报告。希望他们能够修复这个问题。我无法确定我是否真的记得在vs2010测试版中存在这个bug。 - noobish
2
哦,微软,你真的这样吗?!在4.1版本中?!+1 - David
1
那个 Linq-To-SQL 的解决方法似乎不起作用(尝试使用 Guid?)。在 L2S 中使用 Entities-Workaround 可以解决问题,但会生成可怕的 SQL。我不得不在代码中加入 if 语句 (var result = from ...; if(value.HasValue) result = result.Where(e => e.something == value) else result = result.Where(e => e.something == null); - Michael Stum
5
实际上,Object.Equals 的作用是在 (where Object.Equals(entry.something,value)) 中进行比较。 - Michael Stum
5
有人找到 EF 4.5/5.0 中这个所谓的修复位置了吗?我正在使用 5.0,但它仍然行为异常。 - Shaul Behr
显示剩余10条评论

17

自从 Entity Framework 5.0 版本以后,你可以使用以下代码来解决你的问题:

public abstract class YourContext : DbContext
{
  public YourContext()
  {
    (this as IObjectContextAdapter).ObjectContext.ContextOptions.UseCSharpNullComparisonBehavior = true;
  }
}

这应该解决您的问题,因为Entity Framework将使用“类似于C#的”null比较。


16

有一个稍微简单的解决方法可以在 LINQ to Entities 中使用:

var result = from entry in table
         where entry.something == value || (value == null && entry.something == null)
         select entry;
这有效是因为,正如AZ所指出的,LINQ to Entities将x==null(即与null常量的等号比较)特殊处理,并将其转换为x IS NULL。
我们目前正在考虑更改此行为,以自动引入补偿比较,如果等式的两侧都是可空的。但是有几个挑战:
1. 这可能会破坏已经依赖于现有行为的代码。 2. 新的翻译可能会影响现有查询的性能,即使很少使用空参数。
无论如何,我们是否会处理这个问题,将在很大程度上取决于客户分配给它的相对优先级。如果您关心此问题,请在我们的新功能建议网站上为其投票:https://data.uservoice.com

9

如果它是可空类型,也许尝试使用HasValue属性?

var result = from entry in table
                 where !entry.something.HasValue
                 select entry;

虽然这里没有EF来测试,但是我有一个建议 =)


1
嗯...这仅在您只寻找空值时有效,但使用 == null 也不会受到错误的影响。关键是要按变量的值进行过滤,其值可能为 null,并且使 null 值找到 null 记录。 - Dave Cousineau
1
你的回答拯救了我。我忘记在实体模型类上使用可空类型,所以(x => x.Column == null)无法正常工作。 :) - Reuel Ribeiro
这会导致 System.NullReferenceException 异常,因为对象已经是 null! - TiyebM

5

5

处理空比较时,使用Object.Equals()而不是==

参考此链接


这在 Linq-To-Sql 中完美运作,并且生成了正确的 SQL(其他一些答案会生成可怕的 SQL 或错误的结果)。 - Michael Stum
假设我想与null进行比较,Object.Equals(null),如果Object本身为null会怎样? - TiyebM

4

指出所有Entity Framework < 6.0的建议都会生成一些笨拙的SQL。请参见第二个示例以获得“干净”的解决方法。

荒谬的解决办法

// comparing against this...
Foo item = ...

return DataModel.Foos.FirstOrDefault(o =>
    o.ProductID == item.ProductID
    // ridiculous < EF 4.5 nullable comparison workaround https://dev59.com/F3RB5IYBdhLWcg3wQFLu#2541042
    && item.ProductStyleID.HasValue ? o.ProductStyleID == item.ProductStyleID : o.ProductStyleID == null
    && item.MountingID.HasValue ? o.MountingID == item.MountingID : o.MountingID == null
    && item.FrameID.HasValue ? o.FrameID == item.FrameID : o.FrameID == null
    && o.Width == w
    && o.Height == h
    );

在SQL中的结果如下:

SELECT TOP (1) [Extent1].[ID]                 AS [ID],
       [Extent1].[Name]               AS [Name],
       [Extent1].[DisplayName]        AS [DisplayName],
       [Extent1].[ProductID]          AS [ProductID],
       [Extent1].[ProductStyleID]     AS [ProductStyleID],
       [Extent1].[MountingID]         AS [MountingID],
       [Extent1].[Width]              AS [Width],
       [Extent1].[Height]             AS [Height],
       [Extent1].[FrameID]            AS [FrameID],
FROM   [dbo].[Foos] AS [Extent1]
WHERE  (CASE
  WHEN (([Extent1].[ProductID] = 1 /* @p__linq__0 */)
        AND (NULL /* @p__linq__1 */ IS NOT NULL)) THEN
    CASE
      WHEN ([Extent1].[ProductStyleID] = NULL /* @p__linq__2 */) THEN cast(1 as bit)
      WHEN ([Extent1].[ProductStyleID] <> NULL /* @p__linq__2 */) THEN cast(0 as bit)
    END
  WHEN (([Extent1].[ProductStyleID] IS NULL)
        AND (2 /* @p__linq__3 */ IS NOT NULL)) THEN
    CASE
      WHEN ([Extent1].[MountingID] = 2 /* @p__linq__4 */) THEN cast(1 as bit)
      WHEN ([Extent1].[MountingID] <> 2 /* @p__linq__4 */) THEN cast(0 as bit)
    END
  WHEN (([Extent1].[MountingID] IS NULL)
        AND (NULL /* @p__linq__5 */ IS NOT NULL)) THEN
    CASE
      WHEN ([Extent1].[FrameID] = NULL /* @p__linq__6 */) THEN cast(1 as bit)
      WHEN ([Extent1].[FrameID] <> NULL /* @p__linq__6 */) THEN cast(0 as bit)
    END
  WHEN (([Extent1].[FrameID] IS NULL)
        AND ([Extent1].[Width] = 20 /* @p__linq__7 */)
        AND ([Extent1].[Height] = 16 /* @p__linq__8 */)) THEN cast(1 as bit)
  WHEN (NOT (([Extent1].[FrameID] IS NULL)
             AND ([Extent1].[Width] = 20 /* @p__linq__7 */)
             AND ([Extent1].[Height] = 16 /* @p__linq__8 */))) THEN cast(0 as bit)
END) = 1

不可思议的解决方案

如果你想生成更清晰的SQL,可以使用以下方法:

// outrageous < EF 4.5 nullable comparison workaround https://dev59.com/F3RB5IYBdhLWcg3wQFLu#2541042
Expression<Func<Foo, bool>> filterProductStyle, filterMounting, filterFrame;
if(item.ProductStyleID.HasValue) filterProductStyle = o => o.ProductStyleID == item.ProductStyleID;
else filterProductStyle = o => o.ProductStyleID == null;

if (item.MountingID.HasValue) filterMounting = o => o.MountingID == item.MountingID;
else filterMounting = o => o.MountingID == null;

if (item.FrameID.HasValue) filterFrame = o => o.FrameID == item.FrameID;
else filterFrame = o => o.FrameID == null;

return DataModel.Foos.Where(o =>
    o.ProductID == item.ProductID
    && o.Width == w
    && o.Height == h
    )
    // continue the outrageous workaround for proper sql
    .Where(filterProductStyle)
    .Where(filterMounting)
    .Where(filterFrame)
    .FirstOrDefault()
    ;

能够得到你最初想要的结果:

SELECT TOP (1) [Extent1].[ID]                 AS [ID],
           [Extent1].[Name]               AS [Name],
           [Extent1].[DisplayName]        AS [DisplayName],
           [Extent1].[ProductID]          AS [ProductID],
           [Extent1].[ProductStyleID]     AS [ProductStyleID],
           [Extent1].[MountingID]         AS [MountingID],
           [Extent1].[Width]              AS [Width],
           [Extent1].[Height]             AS [Height],
           [Extent1].[FrameID]            AS [FrameID],
FROM   [dbo].[Foos] AS [Extent1]
WHERE  ([Extent1].[ProductID] = 1 /* @p__linq__0 */)
   AND ([Extent1].[Width] = 16 /* @p__linq__1 */)
   AND ([Extent1].[Height] = 20 /* @p__linq__2 */)
   AND ([Extent1].[ProductStyleID] IS NULL)
   AND ([Extent1].[MountingID] = 2 /* @p__linq__3 */)
   AND ([Extent1].[FrameID] IS NULL)

在 SQL 上运行的代码将更清洁和更快,但 EF 将为每个组合生成和缓存新的查询计划,然后再将其发送到 SQL 服务器,这使它比其他解决方法更慢。 - Burak Tamtürk

2
var result = from entry in table
                     where entry.something == null
                     select entry;

上述查询按预期工作。它正确地生成了IS NULL。然而,我的生产代码是这样的:
var value = null;
var result = from entry in table
                         where entry.something == value
                         select entry;

生成的SQL语句为 something = @p; @p = NULL。看起来EF正确地转换了常量表达式,但如果涉及变量,则会像普通比较一样处理。实际上这是有道理的。


1

个人而言,我更喜欢:

var result = from entry in table    
             where (entry.something??0)==(value??0)                    
              select entry;

结束

var result = from entry in table
             where (value == null ? entry.something == null : entry.something == value)
             select entry;

因为它可以防止重复--虽然这并不是数学上的精确,但在大多数情况下都很适用。


1

看起来Linq2Sql也有这个“问题”。由于ANSI NULLS是ON还是OFF,这种行为似乎有一个合理的原因,但令人困惑的是,一个简单的“== null”实际上会按照你的期望工作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接