2009-02-25 11 views
39

Ich habe eine Kategorie-Entität, die ein Nullable ParentId Feld hat. Wenn die unten beschriebene Methode ausgeführt wird und die categoryId null ist, scheint das Ergebnis null zu sein, jedoch gibt es Kategorien mit einem ParentId-Wert von null.Vergleichen NULL-Typen in Linq zu Sql

Was ist das Problem hier, was fehlt mir?

public IEnumerable<ICategory> GetSubCategories(long? categoryId) 
{ 
    var subCategories = this.Repository.Categories.Where(c => c.ParentId == categoryId) 
     .ToList().Cast<ICategory>(); 

    return subCategories; 
} 

By the way, wenn ich die Bedingung (c.ParentId == null) zu ändern, scheint Ergebnis normal.

+1

Ich fand einen Weg ... wird aktualisiert ... –

Antwort

27

Das erste, was zu tun ist, ist Protokollierung, um zu sehen, was TSQL generiert wurde; zum Beispiel:

ctx.Log = Console.Out; 

LINQ-to-SQL scheint nulls ein wenig inkonsistent (abhängig von den wörtlichen vs Wert) zu behandeln:

using(var ctx = new DataClasses2DataContext()) 
{ 
    ctx.Log = Console.Out; 
    int? mgr = (int?)null; // redundant int? for comparison... 
    // 23 rows: 
    var bosses1 = ctx.Employees.Where(x => x.ReportsTo == (int?)null).ToList(); 
    // 0 rows: 
    var bosses2 = ctx.Employees.Where(x => x.ReportsTo == mgr).ToList(); 
} 

Also alles, was ich vorschlagen, ist die Top-Form mit Nullen verwenden kann!

dh

Expression<Func<Category,bool>> predicate; 
if(categoryId == null) { 
    predicate = c=>c.ParentId == null; 
} else { 
    predicate = c=>c.ParentId == categoryId; 
} 
var subCategories = this.Repository.Categories 
      .Where(predicate).ToList().Cast<ICategory>(); 

Update - ich habe es "richtig" mit einem benutzerdefinierten Expression arbeiten:

static void Main() 
    { 
     ShowEmps(29); // 4 rows 
     ShowEmps(null); // 23 rows 
    } 
    static void ShowEmps(int? manager) 
    { 
     using (var ctx = new DataClasses2DataContext()) 
     { 
      ctx.Log = Console.Out; 
      var emps = ctx.Employees.Where(x => x.ReportsTo, manager).ToList(); 
      Console.WriteLine(emps.Count); 
     } 
    } 
    static IQueryable<T> Where<T, TValue>(
     this IQueryable<T> source, 
     Expression<Func<T, TValue?>> selector, 
     TValue? value) where TValue : struct 
    { 
     var param = Expression.Parameter(typeof (T), "x"); 
     var member = Expression.Invoke(selector, param); 
     var body = Expression.Equal(
       member, Expression.Constant(value, typeof (TValue?))); 
     var lambda = Expression.Lambda<Func<T,bool>>(body, param); 
     return source.Where(lambda); 
    } 
+2

Es scheint, es gibt keinen besseren Weg, damit umzugehen. Vielen Dank! –

+2

Ich stieß genau auf dasselbe Problem, machte die gleiche Problemumgehung und wollte gerade fragen, ob es einen besseren Weg gäbe. Sieht aus, als gäbe es nicht :( Dieses Verhalten ist wirklich kontra intuitiv. –

+2

Ich würde sagen, die Inkonsistenz zwischen Literalen und Variablen ist schlimmer, dass die Zähler intuitiv. Danke für die Bestätigung meines Verdachts. +1 – Jodrell

5

Meine Vermutung ist, dass es aufgrund eines eher gemeinsames Attribut von DBMS - Nur weil zwei Dinge null sind, heißt das nicht, dass sie gleich sind.

ein bisschen zu erarbeiten, versuchen Sie diese zwei Abfragen ausführen:

SELECT * FROM TABLE WHERE field = NULL 

SELECT * FROM TABLE WHERE field IS NULL 

Der Grund für das „IS NULL“ Konstrukt ist, dass in der Welt DBMS, NULL = NULL, da die Bedeutung von NULL ist, dass die Wert ist nicht definiert. Da NULL nicht definiert ist, können Sie nicht sagen, dass zwei Nullwerte gleich sind, da Sie per definitionem nicht wissen, was sie sind.

Wenn Sie explizit nach "field == NULL" suchen, konvertiert LINQ das wahrscheinlich in "Feld IS NULL". Aber wenn Sie eine Variable verwenden, vermute ich, dass LINQ diese Konvertierung nicht automatisch ausführt.

Hier ist an MSDN forum post mit mehr Informationen zu diesem Problem.

Sieht aus wie ein guter „betrügen“ ist Ihr Lambda zu ändern wie folgt aussehen:

c => c.ParentId.Equals(categoryId) 
+1

Sie können das Verhalten von NULL = NULL in MSSQL ändern, indem Sie ansi nulls switch setzen. Siehe: http://msdn.microsoft.com/de-de us/library/aa259229 (SQL.80) .aspx –

+0

nein! immer noch nichts! :/ –

+4

Sie müssen object.Equals (a, b) verwenden, was für mich funktioniert, wo a.Equals (b) nicht –

1

Was ist so etwas wie dies einfacher?

public IEnumerable<ICategory> GetSubCategories(long? categoryId) 
{ 
    var subCategories = this.Repository.Categories.Where(c => (!categoryId.HasValue && c.ParentId == null) || c.ParentId == categoryId) 
     .ToList().Cast<ICategory>(); 

    return subCategories; 
} 
52

Andere Art und Weise:

Where object.Equals(c.ParentId, categoryId) 

oder

Where (categoryId == null ? c.ParentId == null : c.ParentId == categoryId) 
+0

Das hat gut funktioniert –

+0

Das funktioniert für mich perfekt.Also, sollten wir standardmäßig Equals (x, y) anstelle von "==" in unserem Prädikat verwenden oder gibt es andere Probleme mit Equals? – AngieM

6

Sie müssen Operator verwenden Equals:

var subCategories = this.Repository.Categories.Where(c => c.ParentId.Equals(categoryId)) 
     .ToList().Cast<ICategory>(); 

Equals fot Nullable Types wieder dreht wahr wenn:

  • Die HasValue Eigenschaft ist falsch, und der andere Parameter ist null. Das heißt, zwei Nullwerte sind per Definition gleich.
  • Die HasValue-Eigenschaft ist wahr, und der von der Value-Eigenschaft zurückgegebene Wert entspricht dem anderen Parameter.

und gibt falsche wenn:

  • Die HasValue Eigenschaft für die aktuelle Nullable Struktur ist wahr, und der andere Parameter ist null.
  • Die HasValue-Eigenschaft für die aktuelle Nullable-Struktur ist false und der andere Parameter ist nicht null.
  • Die HasValue-Eigenschaft für die aktuelle Nullable-Struktur ist wahr und der von der Value-Eigenschaft zurückgegebene Wert ist nicht identisch mit dem anderen Parameter.

Mehr Infos hier Nullable<.T>.Equals Method

+0

Dies ist eine richtige Antwort – tggm

+1

Ich habe dies in LinqPad getestet und das scheint nicht zu funktionieren. Wenn Sie das 'Null' Literal übergeben, ist die sql Tests Categories.ParentID NULL wie Sie erwarten würde. Wenn Sie jedoch eine Variable übergeben, wird Categories.ParentID = p0 getestet, was nicht funktioniert, wenn p0 null ist. Die Methode object.Equals (Categories.ParentID, value) von @ariel funktionierte jedoch gut. –

1

Oder Sie können einfach diese verwenden. Es wird auch

Where((!categoryId.hasValue && !c.ParentId.HasValue) || c.ParentId == categoryId) 
0

Linq to Entities zu einer schöneren SQL-Abfrage übersetzen unterstützt Null Coelescing (??) so nur die Null-on the fly auf einen Standardwert konvertieren.

Where(c => c.ParentId == categoryId ?? 0)