2013-06-13 18 views
5

Die Methode LINQ Join() mit Nullable<int> für TKey überspringt Null-Schlüsselübereinstimmungen. Was fehlt mir in der Dokumentation? Ich weiß, dass ich zu SelectMany() wechseln kann, ich bin nur neugierig, warum diese Gleichheitsoperation funktioniert wie SQL und nicht wie C# seit so nah wie ich sagen kann, funktioniert die EqualityComparer<int?>.Default genau so, wie ich es für Null-Werte erwarten würde.LINQ Beitreten für einen Nullable-Schlüssel

http://msdn.microsoft.com/en-us/library/bb534675.aspx

using System; 
using System.IO; 
using System.Linq; 
using System.Collections.Generic; 

public class dt 
{ 
    public int? Id; 
    public string Data; 
} 

public class JoinTest 
{ 
    public static int Main(string [] args) 
    { 
     var a = new List<dt> 
     { 
      new dt { Id = null, Data = "null" }, 
      new dt { Id = 1, Data = "1" }, 
      new dt { Id = 2, Data = "2" } 
     }; 

     var b = new List<dt> 
     { 
      new dt { Id = null, Data = "NULL" }, 
      new dt { Id = 2, Data = "two" }, 
      new dt { Id = 3, Data = "three" } 
     }; 

     //Join with null elements 
     var c = a.Join(b, 
      dtA => dtA.Id, 
      dtB => dtB.Id, 
      (dtA, dtB) => new { aData = dtA.Data, bData = dtB.Data }).ToList(); 
     // Output: 
     // 2 two 
     foreach (var aC in c) 
      Console.WriteLine(aC.aData + " " + aC.bData); 
     Console.WriteLine(" "); 

     //Join with null elements converted to zero 
     c = a.Join(b, 
      dtA => dtA.Id.GetValueOrDefault(), 
      dtB => dtB.Id.GetValueOrDefault(), 
      (dtA, dtB) => new { aData = dtA.Data, bData = dtB.Data }).ToList(); 

     // Output: 
     // null NULL 
     // 2 two 
     foreach (var aC in c) 
      Console.WriteLine(aC.aData + " " + aC.bData); 

     Console.WriteLine(EqualityComparer<int?>.Default.Equals(a[0].Id, b[0].Id)); 
     Console.WriteLine(EqualityComparer<object>.Default.Equals(a[0].Id, b[0].Id)); 
     Console.WriteLine(a[0].Id.Equals(b[0].Id)); 

     return 0; 
    } 
} 

Antwort

4

Enumerable.Join verwendet JoinIterator (private Klasse) passenden Elemente iterieren. JoinIterator verwendet Lookup<TKey, TElement> für die Erstellung von Abfragen von Sequenzschlüssel: hier

internal static Lookup<TKey, TElement> CreateForJoin(
    IEnumerable<TElement> source, 
    Func<TElement, TKey> keySelector, 
    IEqualityComparer<TKey> comparer) 
{ 
    Lookup<TKey, TElement> lookup = new Lookup<TKey, TElement>(comparer); 
    foreach (TElement local in source) 
    { 
     TKey key = keySelector(local); 
     if (key != null) // <--- Here 
     { 
      lookup.GetGrouping(key, true).Add(local); 
     } 
    } 
    return lookup; 
} 

Interessanter Teil Schlüssel wird übersprungen, die null sind. Aus diesem Grund haben Sie nur eine Übereinstimmung, wenn Sie keinen Standardwert angeben.


Sieht aus wie ich den Grund für ein solches Verhalten gefunden habe. Lookup verwendet Standard EqualityComparer, die 0 sowohl für den Schlüssel wird die null und Schlüssel ist die 0 ist:

int? keyA = 0; 
var comparer = EqualityComparer<int?>.Default; 
int hashA = comparer.GetHashCode(keyA) & 0x7fffffff; // from Lookup class 
int? keyB = null; 
int hashB = comparer.GetHashCode(keyB) & 0x7fffffff; 
Console.WriteLine(hashA); // 0 
Console.WriteLine(hashB); // 0 

Möglicherweise übersprungen nulls passende null und 0 Tasten zu vermeiden.

+0

Habe ich das irgendwo in der Dokumentation vermisst? – ryancerium

+0

@ryancerium gerade überprüft msdn, sieht aus, als ob dieses Verhalten nicht erwähnt wird. Aber ich suche Quellen mit Reflector und sehe diese Implementierung. –

+0

Das scheint mir eine ziemlich ernste Unterlassung in der Dokumentation zu sein. Danke, dass Sie sich das für mich angesehen haben. – ryancerium

0

Ich denke, dass es so gemacht wird, wo you can't join on null keys, they are just ignored das Verhalten von Datenbanken übereinstimmen. There are workarounds, um diese Einschränkung zu umgehen, die leider nicht in LINQ geschrieben werden kann.

Sie müssen Ihre Abfrage so schreiben, dass keiner der Schlüssel tatsächlich null ist. Sie können das einfach tun, indem Sie den Wert in ein anderes Objekt einfügen, das auf Gleichheit verglichen werden kann (z. B. ein Tupel oder anonymes Objekt).

//Join with null elements 
var c = a.Join(b, 
    dtA => Tuple.Create(dtA.Id), 
    dtB => Tuple.Create(dtB.Id), 
    (dtA, dtB) => new { aData = dtA.Data, bData = dtB.Data }).ToList(); 
+0

Cleveres Workaround. – ryancerium

+0

Ups, ich denke, ich habe deine Frage falsch gelesen. Ich dachte, du fragst, warum es sich so verhielt, nicht warum sich die LINQ-to-Objects-Implementierung wie LINQ-to-Entities/-SQL-Implementierungen verhielt. Die Antwort dafür, Konsistenz. –

Verwandte Themen