【问题标题】:How can I query for null values in entity framework?如何在实体框架中查询空值?
【发布时间】:2009-03-25 16:46:24
【问题描述】:

我想执行这样的查询

   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 正确翻译了常量表达式,但如果涉及变量,它会将其视为普通比较。其实有道理。我会结束这个问题。

【问题讨论】:

  • 我认为这没有什么意义......连接器应该有点聪明,而不是要求我们做它的工作:在 SQL 中对正确的 C# 查询执行正确的翻译。这会产生意想不到的行为。
  • 我和 Julien 在一起,这是 EF 的失败
  • 这是标准的失败,而且现在它只会变得更糟,因为与 null 的比较永久导致从 SQL Server 2016 开始未定义,并且 ANSI NULL 永久设置为打开。 Null 可能表示未知值,但“null”本身并不是未知值。空值与空值的比较应该绝对得出真值,但不幸的是,该标准背离了常识以及布尔逻辑。

标签: .net entity-framework ado.net


【解决方案1】:

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,但仅在与变量比较时)要么需要更改为不依赖该行为,要么设置@ 987654324@ 为 false 以使用旧的损坏行为。

【讨论】:

  • 我对错误报告投了赞成票。希望他们能解决这个问题。我不能说我真的记得这个错误存在于 vs2010 测试版中......
  • 哦,来吧微软...真的吗?!?!?在 4.1 版中?!?! +1
  • Linq-To-SQL 解决方法似乎不起作用(尝试使用 Guid?)。使用 Entities-Workaround 在 L2S 中有效,但会生成可怕的 SQL。我必须在代码 (var result = from ...; if(value.HasValue) result = result.Where(e => e.something == value) else result = result.Where(e => e.something == null); 中做一个 if 语句
  • Object.Equals 实际上有效 (where Object.Equals(entry.something,value))
  • @leen3o(或其他任何人) - 有没有人发现这个所谓的修复在 EF 4.5/5.0 中的位置?我正在使用 5.0,但它仍然行为不端。
【解决方案2】:

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

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

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

【讨论】:

    【解决方案3】:

    有一个更简单的解决方法适用于 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

    【讨论】:

      【解决方案4】:

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

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

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

      【讨论】:

      • 嗯...这仅在您只是在寻找空值时才有效,但是使用== null 无论如何都不会受到错误的影响。重点是通过一个变量的值进行过滤,该变量的值可能为空,并让空值找到空记录。
      • 你的回答救了我。我忘记在我的实体模型类上使用可为空的类型,无法使 (x => x.Column == null) 工作。 :)
      • 这给出了System.NullReferenceException ,因为对象已经为空!
      【解决方案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

      【讨论】:

        【解决方案6】:

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

        检查这个reference

        【讨论】:

        • 这在 Linq-To-Sql 中完美运行,并且还生成了正确的 SQL(这里的一些其他答案会生成可怕的 SQL 或错误的结果)。
        • 假设我想与nullObject.Equals(null)进行比较,如果Object本身为空怎么办?
        【解决方案7】:

        指出所有 Entity Framework

        荒谬的解决方法

        // 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 server 之前为每个组合生成并缓存新的查询计划,这使得它比其他解决方法慢。
        【解决方案8】:
        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 = 空。似乎 EF 正确翻译了常量表达式,但如果涉及变量,它会将其视为普通比较。其实有道理。

        【讨论】:

          【解决方案9】:

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

          【讨论】:

            【解决方案10】:

            就个人而言,我更喜欢:

            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;
            

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

            【讨论】:

              【解决方案11】:

              我无法评论 diega 的帖子,但在此处介绍的不同解决方案中,divega 的解决方案产生了最好的 SQL。无论是性能还是长度。我刚刚检查了 SQL Server Profiler 并查看了执行计划(使用“SET STATISTICS PROFILE ON”)。

              【讨论】:

                【解决方案12】:

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

                我使用了这个解决方法(适用于 MSSQL 2012,但 ANSI NULLS 设置可能会在任何未来的 MSSQL 版本中被弃用)。

                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,这将立即停止运行,以防警告不明确。
                【解决方案13】:

                如果你喜欢像我一样使用方法 (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;
                

                【讨论】:

                  【解决方案14】:
                  var result = from entry in table    
                               where entry.something == value||entry.something == null                   
                                select entry;
                  

                  使用它

                  【讨论】:

                  • 这是非常错误的,因为它会选择值匹配的所有条目以及某些内容为空的所有条目,即使您要求一个值。
                  猜你喜欢
                  • 1970-01-01
                  • 2013-10-19
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多