.net - 我如何查詢實體框架中的空值?




entity-framework ado.net (10)

Linq-to-SQL的解決方法:

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

針對Linq-to-Entities(ouch!)的解決方法:

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

這是一個令我討厭的錯誤,它幾次讓我感到困擾。 如果此錯誤也影響到您,請訪問UserVoice上錯誤報告,並讓Microsoft知道此錯誤也影響了您。

編輯: 這個錯誤正在EF 4.5中修復 ! 感謝大家提升這個bug!

為了向後兼容,它將被選中 - 您需要手動啟用設置才能使entry == value工作。 目前還沒有關於這個設置的信息。 敬請關注!

編輯2:根據EF團隊的這篇文章這個問題已經在EF6中修復了! 哇噢!

我們改變了EF6的默認行為來補償三值邏輯。

這意味著依賴於舊行為的現有代碼null != null ,但僅在與變量進行比較時)需要更改為不依賴於該行為,或將UseCSharpNullComparisonBehavior設置為false以使用舊的中斷行為。

我想執行這樣的查詢

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

並獲得一個IS NULL生成。

編輯:在前兩個答案後,我覺得有必要澄清,我使用實體框架,而不是LINQ到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 something = @p; @p = NULL 。 看起來EF正確地翻譯了常量表達式,但是如果涉及一個變量,它就像正常的比較一樣對待它。 實際上有意義。 我會解決這個問題


不幸的是在實體框架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();
            }
        }
    }
}

應該指出,這是一個骯髒的解決方法,但它可以非常快速地執行並適用於所有查詢。


如果它是可以為空的類型,也許嘗試使用HasValue屬性?

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

雖然沒有任何EF在這裡測試...只是一個建議=)


就個人而言,我更喜歡:

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;

因為它可以防止重複 - 雖然這在數學上不是確切的,但它適合大多數情況。


我無法評論divega的文章,但在這裡介紹的不同解決方案中,divega的解決方案產生了最好的SQL。 表現明智和長度明智。 我剛剛查看了SQL Server Profiler並查看了執行計劃(使用“SET STATISTICS PROFILE ON”)。


指出所有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://.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://.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)

看起來Linq2Sql也有這個“問題”。 看起來,由於ANSI NULL是ON還是OFF,導致此行為存在一個合理的原因,但它令人驚訝,為什麼直接的“== null”實際上會如您所期望的那樣工作。


處理空比較使用Object.Equals()而不是==

檢查這個reference



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

使用它





ado.net