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

时间:2009-03-25 16:46:24

标签: .net entity-framework ado.net

我想执行像这样的查询

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

并生成IS NULL

编辑: 在前两个答案之后,我觉得有必要澄清我正在使用实体框架 而不是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正确地转换了常量表达式,但是如果涉及变量,它就像正常比较一样处理它。实际上是有道理的。我将结束这个问题

14 个答案:

答案 0 :(得分:123)

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 report on UserVoice并告知Microsoft此错误也会对您造成影响。


修改: This bug is being fixed in EF 4.5!谢谢大家提出这个错误!

为了向后兼容,它将选择加入 - 您需要手动启用设置才能使entry == value正常工作。还没有关于这个设置是什么的消息。请继续关注!


编辑2:根据EF小组的this post此问题已在EF6中修复!哇噢!

  

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

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

答案 1 :(得分:17)

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

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

这可以解决您的问题,因为实体Framerwork将使用'C#like'null比较。

答案 2 :(得分: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参数,新翻译也可能会影响现有查询的性能。
  3. 无论如何,我们是否开始研究这将取决于客户分配给它的相对优先级。如果您关心这个问题,我建议您在我们的新功能建议网站上投票:https://data.uservoice.com

答案 3 :(得分:8)

如果是可以为空的类型,可以尝试使用HasValue属性吗?

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

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

答案 4 :(得分:5)

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

MSDN参考LINQ to SQL: .NET Language-Integrated Query for Relational Data

答案 5 :(得分:5)

使用Object.Equals()代替==

来处理Null Comparisons

检查此reference

答案 6 :(得分:3)

指出所有实体框架< 6.0建议产生一些尴尬的SQL。请参阅第二个示例" clean"固定。

荒谬的解决方法

// 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)

答案 7 :(得分: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正确地转换了常量表达式,但是如果涉及变量,它就像正常比较一样处理它。实际上是有道理的。

答案 8 :(得分:1)

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

答案 9 :(得分: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;

因为它可以防止重复 - 虽然这在数学上并不准确,但它适合大多数情况。

答案 10 :(得分:0)

我无法对divega的帖子发表评论,但在此处介绍的不同解决方案中,divega的解决方案可以产生最好的SQL。表现明智和长度明智。我刚刚查看了SQL Server Profiler并查看了执行计划(“SET STATISTICS PROFILE ON”)。

答案 11 :(得分: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();
            }
        }
    }
}

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

答案 12 :(得分: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;

答案 13 :(得分:-1)

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

使用