2009-02-25 19 views
39

Mam jednostkę kategorii, która ma pole Nullable ParentId. Gdy wykonywana jest poniższa metoda, a identyfikator kategorii ma wartość null, wynik wydaje się zerowy, ale istnieją kategorie, które mają zerową wartość ParentId.Porównaj typy null od Linq do Sql

W czym jest problem, czego mi brakuje?

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

    return subCategories; 
} 

Przy okazji, gdy zmienię warunek na (c.ParentId == null), wynik wydaje się być normalny.

+1

Znalazłem sposób ... zaktualizuje ... –

Odpowiedz

27

Pierwszą rzeczą do zrobienia jest rejestracja, aby zobaczyć, co wygenerowało TSQL; na przykład:

ctx.Log = Console.Out; 

LINQ-to-SQL wydaje się traktować null trochę niekonsekwentnie (w zależności od dosłowne vs wartości):

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

Więc wszystko co mogę zasugerować to użyć górną formę z null!

tj

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 - Mam to działa "prawidłowo" przy użyciu niestandardowego Expression:

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

Wygląda na to, że nie ma lepszego sposobu na rozwiązanie tego problemu. Dzięki! –

+2

Wpadłem na dokładnie ten sam problem, zrobiłem to samo i zamierzałem zapytać, czy jest lepszy sposób na zrobienie tego. Wygląda na to, że nie ma :( To zachowanie jest naprawdę sprzeczne z intuicją –

+2

Powiedziałbym, że niespójność między literałami i zmiennymi jest gorsza od intuicyjnego.Proszę o potwierdzenie mojego podejrzenia +1 – Jodrell

5

Domyślam się, że to ze względu na dość wspólnym atrybutem DBMS - Tylko dlatego, że dwie rzeczy są jednocześnie zerowe, nie oznacza, że ​​są równe.

Aby rozwinąć trochę, spróbuj wykonujące te dwa pytania:

SELECT * FROM TABLE WHERE field = NULL 

SELECT * FROM TABLE WHERE field IS NULL 

Powodem „IS NULL” konstrukcji jest to, że w świecie DBMS, NULL = NULL ponieważ sens NULL jest to, że wartość jest niezdefiniowana. Ponieważ NULL oznacza niezdefiniowane, nie możesz powiedzieć, że dwie wartości puste są równe, ponieważ z definicji nie wiesz, czym one są.

Kiedy wyraźnie zaznaczysz "pole == NULL", LINQ prawdopodobnie przekształci to w "pole IS NULL". Ale kiedy używasz zmiennej, domyślam się, że LINQ nie wykonuje automatycznie tej konwersji.

Oto an MSDN forum post z dodatkowymi informacjami na temat tego problemu.

Wygląda dobry „oszukiwać”, aby zmienić swoje lambda wyglądać następująco:

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

Możesz zmienić zachowanie NULL = NULL w MSSQL przez ustawienie przełącznika ansi nulls. Patrz: http://msdn.microsoft.com/en- us/library/aa259229 (SQL.80) .aspx –

+0

nie! wciąż nic! :/ –

+4

Musisz użyć object.Equals (a, b), który pracował dla mnie, gdzie a.Equals (b) nie –

1

Co o czymś prostszym w ten sposób?

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

Inny sposób:

Where object.Equals(c.ParentId, categoryId) 

lub

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

To działało ładnie –

+0

To działa dla mnie doskonale.Więc, czy powinniśmy używać w naszym predykacie równań (x, y), a nie "==", czy też istnieją inne wartości z równymi? – AngieM

6

trzeba użyć operatora wynosi:

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

Równa fot typy dopuszczające wartość null ponownie Okazuje prawdziwą jeżeli:

  • Nieruchomość HasValue jest fałszywa, a drugi parametr ma wartość null. Oznacza to, że dwie wartości puste są z definicji równe.
  • Właściwość HasValue ma wartość true, a wartość zwrócona przez właściwość Wartość jest równa innym parametrom.

i zwraca fałszywy jeżeli:

  • Nieruchomość HasValue dla bieżącego pustych strukturze jest prawdziwy, a drugi parametr ma wartość null.
  • Właściwość HasValue dla bieżącej struktury zerującej ma wartość false, a drugi parametr nie jest pusty.
  • Właściwość HasValue dla bieżącej struktury Nullable jest prawdziwa, a wartość zwrócona przez właściwość Value nie jest równa innym parametrom.

Więcej informacji tutaj Nullable<.T>.Equals Method

+0

To jest odpowiednia odpowiedź – tggm

+1

Testowałem to w LinqPadzie i to nie działa. Jeśli przekazujesz literał "null", sql generuje testy Categories.ParentID JEST NULL, jak można się spodziewać. Ale jeśli przekazujesz zmienną, testuje on Kategorie.ParentID = p0, co nie zadziała, jeśli p0 jest puste. Object.Equals (Categories.ParentID, value) podejście z @ariel działało jednak świetnie. –

1

Albo można po prostu używać. Będzie ona również przekłada się na ładniejszy zapytania sql

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

LINQ do podmiotów obsługuje Null Coelescing (??), więc po prostu przekonwertować wartość null w locie do wartości domyślnej.