Wie führt man einen Linken Outer-Join mit Linq-Erweiterungsmethoden durch?

Angenommen, ich habe eine linke äußere Verbindung als solche:

from f in Foo join b in Bar on f.Foo_Id equals b.Foo_Id into g from result in g.DefaultIfEmpty() select new { Foo = f, Bar = result } 

Wie würde ich die gleiche Aufgabe mit Erweiterungsmethoden express? Z.B

 Foo.GroupJoin(Bar, f => f.Foo_Id, b => b.Foo_Id, (f,b) => ???) .Select(???) 

 var qry = Foo.GroupJoin( Bar, foo => foo.Foo_Id, bar => bar.Foo_Id, (x,y) => new { Foo = x, Bars = y }) .SelectMany( x => x.Bars.DefaultIfEmpty(), (x,y) => new { Foo=x.Foo, Bar=y}); 

Da dies die De-facto-SO-Frage für Links-Outer-Joins unter Verwendung der Methoden- (Erweiterungs-) Syntax zu sein scheint, dachte ich, dass ich eine Alternative zu der aktuell ausgewählten Antwort hinzufügen würde, die (meiner Erfahrung nach) häufiger ist, was ich bin nach

 // Option 1: Expecting either 0 or 1 matches from the "Right" // table (Bars in this case): var qry = Foos.GroupJoin( Bars, foo => foo.Foo_Id, bar => bar.Foo_Id, (f,bs) => new { Foo = f, Bar = bs.SingleOrDefault() }); // Option 2: Expecting either 0 or more matches from the "Right" table // (courtesy of currently selected answer): var qry = Foos.GroupJoin( Bars, foo => foo.Foo_Id, bar => bar.Foo_Id, (f,bs) => new { Foo = f, Bars = bs }) .SelectMany( fooBars => fooBars.Bars.DefaultIfEmpty(), (x,y) => new { Foo = x.Foo, Bar = y }); 

Um den Unterschied mit einem einfachen Datensatz anzuzeigen (vorausgesetzt, wir fügen die Werte selbst hinzu):

 List tableA = new List { 1, 2, 3 }; List tableB = new List { 3, 4, 5 }; // Result using both Option 1 and 2. Option 1 would be a better choice // if we didn't expect multiple matches in tableB. { A = 1, B = null } { A = 2, B = null } { A = 3, B = 3 } List tableA = new List { 1, 2, 3 }; List tableB = new List { 3, 3, 4 }; // Result using Option 1 would be that an exception gets thrown on // SingleOrDefault(), but if we use FirstOrDefault() instead to illustrate: { A = 1, B = null } { A = 2, B = null } { A = 3, B = 3 } // Misleading, we had multiple matches. // Which 3 should get selected (not arbitrarily the first)?. // Result using Option 2: { A = 1, B = null } { A = 2, B = null } { A = 3, B = 3 } { A = 3, B = 3 } 

Option 2 entspricht der typischen linken Outer-Join-Definition, ist aber, wie bereits erwähnt, oft unnötig komplex, abhängig vom Datensatz.

Die Gruppenverbindungsmethode ist nicht erforderlich, um das Verbinden zweier Datensätze zu erreichen.

Innerer Join:

 var qry = Foos.SelectMany ( foo => Bars.Where (bar => foo.Foo_id == bar.Foo_id), (foo, bar) => new { Foo = foo, Bar = bar } ); 

Für Left Join fügen Sie einfach DefaultIfEmpty () hinzu

 var qry = Foos.SelectMany ( foo => Bars.Where (bar => foo.Foo_id == bar.Foo_id).DefaultIfEmpty(), (foo, bar) => new { Foo = foo, Bar = bar } ); 

EF wird korrekt in SQL umgewandelt. Für LINQ zu Objekten ist es besser, mit GroupJoin beizutreten, da es Lookup intern verwendet, aber wenn Sie DB abfragen, dann ist das Überspringen von GroupJoin AFAIK als performant.

Personlay für mich ist auf diese Weise besser lesbar als GroupJoin (). SelectMany ()

Sie können eine Erweiterungsmethode wie folgt erstellen:

 public static IEnumerable LeftOuterJoin(this IEnumerable source, IEnumerable other, Func func, Func innerkey, Func res) { return from f in source join b in other on func.Invoke(f) equals innerkey.Invoke(b) into g from result in g.DefaultIfEmpty() select res.Invoke(f, result); } 

Verbessert auf Ocelot20’s Antwort, wenn Sie eine Tabelle haben, bei der Sie sich äußerlich mit dem verbinden, wo Sie nur 0 oder 1 Zeilen davon haben wollen, aber es könnte mehrere haben, müssen Sie Ihre verbundene Tabelle bestellen:

 var qry = Foos.GroupJoin( Bars.OrderByDescending(b => b.Id), foo => foo.Foo_Id, bar => bar.Foo_Id, (f, bs) => new { Foo = f, Bar = bs.FirstOrDefault() }); 

Andernfalls wird die Zeile, die Sie in der Verknüpfung erhalten, zufällig (oder genauer gesagt, je nachdem, welche database zuerst gefunden wird).

Als ich Marc Gravells Antwort in eine Erweiterungsmethode umwandelte, machte ich Folgendes.

 internal static IEnumerable> LeftJoin( this IEnumerable left, IEnumerable right, Func selectKeyLeft, Func selectKeyRight, TRight defaultRight = default(TRight), IEqualityComparer cmp = null) { return left.GroupJoin( right, selectKeyLeft, selectKeyRight, (x, y) => new Tuple>(x, y), cmp ?? EqualityComparer.Default) .SelectMany( x => x.Item2.DefaultIfEmpty(defaultRight), (x, y) => new Tuple(x.Item1, y)); } 

Während die angenommene Antwort funktioniert und Linq für Objekte gut ist, hat es mich gestört, dass die SQL-Abfrage nicht nur eine gerade Linke Äußere Verbindung ist.

Der folgende Code basiert auf dem LinkKit-Projekt , mit dem Sie Ausdrücke übergeben und sie für Ihre Abfrage aufrufen können.

 static IQueryable LeftOuterJoin( this IQueryable source, IQueryable inner, Expression> sourceKey, Expression> innerKey, Expression> result ) { return from a in source.AsExpandable() join b in inner on sourceKey.Invoke(a) equals innerKey.Invoke(b) into c from d in c.DefaultIfEmpty() select result.Invoke(a,d); } 

Es kann wie folgt verwendet werden

 Table1.LeftOuterJoin(Table2, x => x.Key1, x => x.Key2, (x,y) => new { x,y});