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


109

我想执行这样的查询

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

IS NULL生成一个。

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

编辑2号:上述查询按预期方式工作。它会正确生成IS NULL。我的生产代码是

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

并且生成的SQL是something = @p; @p = NULL。似乎EF可以正确转换常量表达式,但是如果涉及到变量,它将像对待普通比较一样对待它。实际上是有道理的。我将结束这个问题


17
我认为这没有什么意义……连接器应该有点聪明,不要要求我们去做:在SQL中执行正确的C#查询的正确转换。这会产生意外的行为。
朱利安N

6
我和Julien在一起,这是EF的失败
贝尔先生,

1
这是标准的失败,并且现在变得更糟的是,与null的比较将永久导致在SQL Server 2016中未定义,并将ANSI NULL永久设置为on。Null可能代表未知值,但“ null”本身并非未知值。空值与空值的比较应该绝对得出true,但是不幸的是,该标准背离了常识和布尔逻辑。
Triynko,2015年

Answers:


126

Linq-to-SQL的解决方法:

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

Linq到实体的解决方法(哎呀!):

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

这是一个讨厌的错误,已经咬了我好几次了。 如果此错误也影响了您,请访问UserVoice上错误报告,并让Microsoft知道此错误也影响了您。


编辑: 此错误已在EF 4.5中修复!感谢大家支持此错误!

为了向后兼容,它将选择启用-您需要手动启用设置才能生效entry == value。尚无关于此设置的消息。敬请关注!


编辑2: 根据EF团队的帖子此问题已在EF6中修复!oo!

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

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


6
我已对错误报告投了赞成票。希望他们能解决这个问题。我不能说我真的记得这个错误存在于vs2010 beta版中
noobish 2010年

2
哦,来吧微软...真的吗?!?!?在4.1版中?!?!+1
David

1
Linq-To-SQL解决方法似乎不起作用(尝试使用Guid?)。使用Entities-Workaround可在L2S中使用,但会生成可怕的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
@ leen3o(或其他任何人)-是否有人在EF 4.5 / 5.0中找到此所谓的修复程序?我正在使用5.0,但仍无法正常工作。
Shaul Behr 2013年

17

从Entity Framework 5.0开始,您可以使用以下代码来解决您的问题:

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

这将解决您的问题,因为Entity Framerwork将使用“ C#like”空比较。


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. 即使很少使用null参数,新的转换也可能影响现有查询的性能。

无论如何,我们是否要着手进行这将在很大程度上取决于客户为其分配的相对优先级。如果您关心此问题,建议您在我们的新功能建议网站https://data.uservoice.com中对其投票。


9

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

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

尽管这里没有任何EF可以测试...只是一个建议=)


1
好吧...这仅在您只寻找null的情况下有效,但是使用== null无论如何都不会被bug击中。重点是要根据变量的值进行过滤,该变量的值可能为null,并让null值查找空记录。
Dave Cousineau

1
你的回答救了我。我忘了在实体模型类上使用可为空的类型,并且无法正常(x => x.Column == null)工作。:)
Reuel Ribeiro

给出System.NullReferenceException ,因为对象allready为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 http://stackoverflow.com/a/2541042/1037948
    && 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 http://stackoverflow.com/a/2541042/1037948
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服务器之前为每个组合生成并缓存新的查询计划,这使其比其他变通方法要慢。
BurakTamtürk,2016年

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是= @p; @p = NULL。似乎EF可以正确转换常量表达式,但是如果涉及到变量,它将像对待普通比较一样对待它。实际上是有道理的。


1

看来Linq2Sql也有此“问题”。似乎由于ANSI NULL是ON还是OFF而存在此行为的正当理由,但令人困惑的是为什么直的“ == null”实际上会按您的预期工作。


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;

因为它可以防止重复-尽管从数学角度上讲并不精确,但它适合大多数情况。


0

我无法评论divega的帖子,但是在这里介绍的不同解决方案中,divega的解决方案可产生最佳的SQL。在性能方面和长度方面均如此。我只是使用SQL Server Profiler并通过查看执行计划来检查(“ SET STATISTICS PROFILE ON”)。


0

不幸的是,在Entity Framework 5 DbContext中,此问题仍未解决。

我使用了这种解决方法(可与MSSQL 2012一起使用,但将来的任何MSSQL版本都可能不建议使用ANSI NULLS设置)。

public class Context : DbContext
{

    public Context()
        : base("name=Context")
    {
        this.Database.Connection.StateChange += Connection_StateChange;
    }

    void Connection_StateChange(object sender, System.Data.StateChangeEventArgs e)
    {
        // Set ANSI_NULLS OFF when any connection is opened. This is needed because of a bug in Entity Framework
        // that is not fixed in EF 5 when using DbContext.
        if (e.CurrentState == System.Data.ConnectionState.Open)
        {
            var connection = (System.Data.Common.DbConnection)sender;
            using (var cmd = connection.CreateCommand())
            {
                cmd.CommandText = "SET ANSI_NULLS OFF";
                cmd.ExecuteNonQuery();
            }
        }
    }
}

应该注意的是,这是一种肮脏的解决方法,但是它可以非常快地实现并适用于所有查询。


如果未清除警告,则在将来版本的SQL Server中将ANSI NULLS永久设置为ON时,此功能将立即停止运行。
Triynko 2015年

0

如果您喜欢像我一样使用方法(lambda)语法,则可以执行以下操作:

var result = new TableName();

using(var db = new EFObjectContext)
{
    var query = db.TableName;

    query = value1 == null 
        ? query.Where(tbl => tbl.entry1 == null) 
        : query.Where(tbl => tbl.entry1 == value1);

    query = value2 == null 
        ? query.Where(tbl => tbl.entry2 == null) 
        : query.Where(tbl => tbl.entry2 == value2);

    result = query
        .Select(tbl => tbl)
        .FirstOrDefault();

   // Inspect the value of the trace variable below to see the sql generated by EF
   var trace = ((ObjectQuery<REF_EQUIPMENT>) query).ToTraceString();

}

return result;

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

用那个


5
这是非常错误的,因为它会选择值匹配的所有条目,而某些东西为null的所有条目,即使您要求输入一个值。
迈克尔·斯托姆
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.