エンティティフレームワークでnull値を照会するにはどうすればよいですか?


109

このようなクエリを実行したい

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

そして、 IS NULL生成。

編集:最初の2つの回答の後、Linq to SQLではなくEntity Frameworkを使用していることを明確にする必要があると感じています。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
私はそれは本当に意味をなさないと思います...コネクタは少し賢く、私たちにその仕事をするように求めてはいけません:正しいC#クエリのSQLで正しい翻訳を実行してください。これにより、予期しない動作が発生します。
Julien N

6
私はジュリアンと一緒です、これはEF側の失敗です
ベル氏

1
これは標準の失敗であり、nullとの比較が恒久的に未定義となり、ANSI NULLが恒久的にオンに設定されたSQL Server 2016の時点で、未定義になっているだけで悪化しています。nullは不明な値を表す場合がありますが、「null」自体は不明な値ではありません。null値とnull値を比較すると、完全にtrueが得られるはずですが、残念ながら、標準はブール論理と同様に常識から逸脱しています。
Triynko

回答:


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;

これは厄介なバグで、何度も私を噛みました。 このバグがあなたにも影響を及ぼしている場合は、UserVoiceバグレポートにアクセスして、このバグがあなたにも影響を与えていることをマイクロソフトに知らせてください。


編集: このバグはEF 4.5で修正されています!このバグに賛成してくれてありがとう!

下位互換性のために、これはオプトインになります-機能させるには手動で設定を有効にする必要がありますentry == value。この設定が何であるかについての言葉はまだありません。乞うご期待!


編集2: EFチームによるこの投稿に よると、この問題はEF6で修正されています!ウフー!

3値のロジックを補正するために、EF6のデフォルトの動作を変更しました。

つまり、古い動作に依存している既存のコードnull != null変数と比較する場合のみ)は、その動作に依存しないように変更するかUseCSharpNullComparisonBehavior、falseに設定して古い壊れた動作を使用する必要があります。


6
バグレポートに投票しました。うまくいけば、彼らはこれを修正します。このバグがvs2010ベータに存在したことを本当に覚えているとは言えません...
10:19にnoobish

2
ああマイクロソフトに来る...本当に?!?!?バージョン4.1では?!?!+1
デビッド

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' 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に変換されるためです。

現在、この動作を変更して、等価の両側がnull可能である場合に自動的に補正比較を導入することを検討しています。ただし、いくつかの課題があります。

  1. これにより、既存の動作に既に依存しているコードが壊れる可能性があります。
  2. 新しい変換は、nullパラメータがほとんど使用されない場合でも、既存のクエリのパフォーマンスに影響を与える可能性があります。

いずれにせよ、これに取り組むかどうかは、お客様がそれに割り当てる相対的な優先順位に大きく依存します。この問題に関心がある場合は、新しい機能提案サイト(https://data.uservoice.com)で投票することをお勧めします。


9

null許容型の場合は、HasValueプロパティを使用してみてください。

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

ただし、ここでテストするEFはありません...単なる提案=)


1
まあ...これはnullを探している場合にのみ機能しますが、それでも使用== nullしてもバグに見舞われることはありません。ポイントは、値がnullの可能性がある変数の値でフィルタリングすることであり、null値でnullレコードを検索します。
Dave Cousineau 2013

1
あなたの答えは私を救った。エンティティモデルクラスでnull許容型を使用するのを忘れており、機能させることができませんでし(x => x.Column == null)た。:)
Reuel Ribeiro 2017

System.NullReferenceException オブジェクトはすでにnullであるため、これはを与えます!
TiyebM


5

Null比較を処理するObject.Equals()には、代わりに==

このリファレンスを確認してください


これはLinq-To-Sqlで完全に機能し、適切なSQLも生成します(他のいくつかの回答では、恐ろしいSQLまたは誤った結果が生成されます)。
Michael Stum

私はcompaireしたいとしnullObject.Equals(null)どのような場合には、Objectそれ自体がnull?
TiyebM

4

Entity Framework <6.0の提案はすべて、厄介なSQLを生成することを指摘しました。「クリーン」な修正については、2番目の例を参照してください。

ばかげた回避策

// 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ürk16年

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プロファイラを使用して実行プランを確認したところ( "SET STATISTICS PROFILE ON"を使用)。


0

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に設定されると、これはすぐに機能を停止します。
Triynko、2015年

0

私と同じようにメソッド(ラムダ)構文を使用したい場合は、次のように同じことを行うことができます。

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であるすべてのエントリを選択するため、これは非常に間違っています。
Michael Stum
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.