Wie kann ich im Entity Framework nach Nullwerten fragen?

109

Ich möchte eine solche Abfrage ausführen

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

und erhalten eine IS NULLgenerierte.

Bearbeitet: Nach den ersten beiden Antworten muss klargestellt werden, dass ich Entity Framework und nicht Linq to SQL verwende. Die object.Equals () -Methode scheint in EF nicht zu funktionieren.

Edit Nr. 2: Die obige Abfrage funktioniert wie vorgesehen. Es wird korrekt generiert IS NULL. Mein Produktionscode war jedoch

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

und das generierte SQL war something = @p; @p = NULL. Es scheint, dass EF den konstanten Ausdruck korrekt übersetzt, aber wenn eine Variable beteiligt ist, behandelt sie ihn wie einen normalen Vergleich. Macht eigentlich Sinn. Ich werde diese Frage schließen

Adrian Zanescu
quelle
17
Ich denke, es macht nicht wirklich Sinn ... Der Connector sollte ein wenig klug sein und uns nicht bitten, seine Arbeit zu erledigen: Führen Sie eine korrekte Übersetzung der richtigen C # -Abfrage in SQL durch. Dies erzeugt ein unerwartetes Verhalten.
Julien N
6
Ich bin mit Julien zusammen, dies ist ein Fehler von EF
Mr Bell
1
Dies ist ein Fehler der Standards, und es wird nur noch schlimmer, wenn der Vergleich mit null ab SQL Server 2016 dauerhaft undefiniert ist und ANSI-NULL-Werte dauerhaft aktiviert sind. Null kann repräsentieren einen unbekannten Wert, aber „null“ selbst ist kein unbekannter Wert. Der Vergleich eines Nullwerts mit einem Nullwert sollte absolut wahr ergeben, aber leider weicht der Standard sowohl vom gesunden Menschenverstand als auch von der Booleschen Logik ab.
Triynko

Antworten:

126

Problemumgehung für Linq-to-SQL:

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

Problemumgehung für Linq-to-Entities (autsch!):

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

Dies ist ein böser Fehler, der mich mehrmals gebissen hat. Wenn dieser Fehler auch Sie betroffen hat, besuchen Sie bitte den Fehlerbericht auf UserVoice und teilen Sie Microsoft mit, dass dieser Fehler auch Sie betroffen hat.


Bearbeiten: Dieser Fehler wird in EF 4.5 behoben ! Vielen Dank an alle für das Upvoting dieses Fehlers!

Aus Gründen der Abwärtskompatibilität wird die Option aktiviert. Sie müssen eine Einstellung manuell aktivieren, damit sie entry == valuefunktioniert. Noch kein Wort darüber, was diese Einstellung ist. Bleib dran!


Edit 2: Nach diesem Beitrag des EF-Teams wurde dieses Problem in EF6 behoben! Woohoo!

Wir haben das Standardverhalten von EF6 geändert, um die dreiwertige Logik zu kompensieren.

Dies bedeutet, dass vorhandener Code, der sich auf das alte Verhalten stützt ( null != nulljedoch nur beim Vergleich mit einer Variablen) , entweder geändert werden muss, um sich nicht auf dieses Verhalten zu verlassen, oder UseCSharpNullComparisonBehaviorauf false gesetzt werden muss, um das alte fehlerhafte Verhalten zu verwenden.

BlueRaja - Danny Pflughoeft
quelle
6
Ich habe den Fehlerbericht abgestimmt. Hoffentlich beheben sie das. Ich kann nicht sagen, dass ich mich wirklich daran erinnere, dass dieser Fehler in der Beta von
vs2010
2
Ach komm schon Microsoft ... wirklich?!?!? In Version 4.1?!?! +1
David
1
Diese Linq-To-SQL-Problemumgehung scheint nicht zu funktionieren (versuchen Sie es mit einem Guid?). Die Verwendung der Entities-Workaround funktioniert in L2S, generiert jedoch horrendes SQL. Ich musste eine if-Anweisung im Code machen(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 funktioniert tatsächlich(where Object.Equals(entry.something,value))
Michael Stum
5
@ leen3o (oder jemand anderes) - Hat jemand schon gefunden, wo sich dieser angebliche Fix in EF 4.5 / 5.0 befindet? Ich benutze 5.0 und es benimmt sich immer noch schlecht.
Shaul Behr
17

Seit Entity Framework 5.0 können Sie folgenden Code verwenden, um Ihr Problem zu lösen:

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

Dies sollte Ihre Probleme lösen, da Entity Framerwork den Nullvergleich "C # like" verwendet.

ITmeze
quelle
16

Es gibt eine etwas einfachere Problemumgehung, die mit LINQ to Entities funktioniert:

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

Dies funktioniert, weil, wie AZ feststellte, LINQ to Entities-Sonderfälle x == null (dh ein Gleichheitsvergleich mit der Nullkonstante) und in x IS NULL übersetzt werden.

Wir erwägen derzeit, dieses Verhalten zu ändern, um die Kompensationsvergleiche automatisch einzuführen, wenn beide Seiten der Gleichheit nullbar sind. Es gibt jedoch einige Herausforderungen:

  1. Dies könnte möglicherweise Code beschädigen, der bereits vom vorhandenen Verhalten abhängt.
  2. Die neue Übersetzung kann die Leistung vorhandener Abfragen beeinträchtigen, selbst wenn ein Nullparameter selten verwendet wird.

Ob wir daran arbeiten können, hängt in jedem Fall stark von der relativen Priorität ab, die unsere Kunden ihm zuweisen. Wenn Sie sich für das Problem interessieren, empfehlen wir Ihnen, auf unserer neuen Website mit Funktionsvorschlägen dafür zu stimmen: https://data.uservoice.com .

Divega
quelle
9

Wenn es sich um einen nullbaren Typ handelt, versuchen Sie möglicherweise, die HasValue-Eigenschaft zu verwenden.

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

Ich habe hier allerdings keine EF zum Testen ... nur ein Vorschlag =)

Svish
quelle
1
Nun ... das funktioniert nur, wenn Sie nur nach Nullen suchen, aber dann wird using == nullohnehin nicht vom Fehler getroffen. Der Punkt besteht darin, nach dem Wert einer Variablen zu filtern, deren Wert möglicherweise null ist, und den Nullwert die Nulldatensätze finden zu lassen.
Dave Cousineau
1
Deine Antwort hat mich gerettet. Ich habe vergessen, einen nullbaren Typ für meine Entitätsmodellklasse zu verwenden, und konnte nicht (x => x.Column == null)funktionieren. :)
Reuel Ribeiro
Dies gibt System.NullReferenceException , da das Objekt bereits null ist!
TiyebM
5

um mit Nullvergleichen umzugehen, verwenden Sie Object.Equals()statt==

Überprüfen Sie diese Referenz

Oscar Cabrero
quelle
Dies funktioniert perfekt in Linq-To-Sql und generiert auch das richtige SQL (einige andere Antworten hier erzeugen horrendes SQL oder falsche Ergebnisse).
Michael Stum
Angenommen , ich möchte compaire mit null, Object.Equals(null)was passiert , wenn die Objectselbst ist null?
TiyebM
4

Es wird darauf hingewiesen, dass alle Entity Framework <6.0-Vorschläge etwas umständliches SQL erzeugen. Siehe zweites Beispiel für "saubere" Korrektur.

Lächerliche Problemumgehung

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

Ergebnisse in SQL wie:

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

Unverschämte Problemumgehung

Wenn Sie saubereres SQL generieren möchten, können Sie Folgendes tun:

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

ergibt in erster Linie das, was Sie wollten:

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)
drzaus
quelle
Der auf SQL ausgeführte Code ist sauberer und schneller, aber EF generiert und speichert einen neuen Abfrageplan für jede Kombination, bevor er an den SQL Server gesendet wird. Dies macht ihn langsamer als andere Problemumgehungen.
Burak Tamtürk
2
var result = from entry in table
                     where entry.something == null
                     select entry;

Die obige Abfrage funktioniert wie vorgesehen. Es generiert korrekt IS NULL. Mein Produktionscode war jedoch

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

und das generierte SQL war etwas = @p; @p = NULL. Es scheint, dass EF den konstanten Ausdruck korrekt übersetzt, aber wenn eine Variable beteiligt ist, behandelt sie ihn wie einen normalen Vergleich. Macht eigentlich Sinn.

Adrian Zanescu
quelle
1

Es scheint, dass Linq2Sql auch dieses "Problem" hat. Es scheint, dass es einen gültigen Grund für dieses Verhalten gibt, weil ANSI NULLs EIN oder AUS sind, aber es verwirrt den Verstand, warum eine gerade "== null" tatsächlich so funktioniert, wie Sie es erwarten würden.

JasonCoder
quelle
1

Persönlich bevorzuge ich:

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

Über

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

weil es Wiederholungen verhindert - obwohl das mathematisch nicht genau ist, aber es passt in den meisten Fällen gut.

Vincent Courcelle
quelle
0

Ich kann den Beitrag von divega nicht kommentieren, aber unter den verschiedenen hier vorgestellten Lösungen produziert die Lösung von divega das beste SQL. Sowohl in Bezug auf die Leistung als auch in Bezug auf die Länge. Ich habe gerade mit SQL Server Profiler nachgesehen und mir den Ausführungsplan angesehen (mit "SET STATISTICS PROFILE ON").

Buginator
quelle
0

Leider ist das Problem in Entity Framework 5 DbContext immer noch nicht behoben.

Ich habe diese Problemumgehung verwendet (funktioniert mit MSSQL 2012, aber die Einstellung ANSI NULLS ist in zukünftigen MSSQL-Versionen möglicherweise veraltet).

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();
            }
        }
    }
}

Es sollte beachtet werden, dass es sich um eine schmutzige Problemumgehung handelt, die jedoch sehr schnell implementiert werden kann und für alle Abfragen funktioniert.

Knaģis
quelle
Dies funktioniert sofort nicht mehr, sobald ANSI NULLS in einer zukünftigen Version von SQL Server dauerhaft auf ON gesetzt ist, falls die Warnung nicht klar war.
Triynko
0

Wenn Sie die Methodensyntax (Lambda) wie ich bevorzugen, können Sie Folgendes tun:

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;
John Meyer
quelle
-1
var result = from entry in table    
             where entry.something == value||entry.something == null                   
              select entry;

verwende das

Andrew
quelle
5
Das ist SEHR falsch, da alle Einträge ausgewählt werden, bei denen der Wert übereinstimmt, UND alle Einträge, bei denen etwas null ist, selbst wenn Sie nach einem Wert fragen.
Michael Stum