Come si esegue un join esterno sinistro usando i metodi di estensione linq

Supponendo che io abbia un join esterno sinistro come tale:

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 } 

Come potrei esprimere lo stesso compito usando i metodi di estensione? Per esempio

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

Poiché questa sembra essere di fatto la domanda SO per i join esterni a sinistra usando la syntax del metodo (estensione), ho pensato di aggiungere un’alternativa alla risposta attualmente selezionata che (almeno nella mia esperienza) è stata più comunemente ciò che sono dopo

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

Per visualizzare la differenza utilizzando un semplice set di dati (supponendo che stiamo unendo i valori stessi):

 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 } 

L’opzione 2 è vera alla definizione tipica del join esterno sinistro, ma come accennato in precedenza è spesso inutilmente complessa a seconda del set di dati.

Il metodo Join del gruppo non è necessario per ottenere l’unione di due set di dati.

Join interno:

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

Per Join sinistro basta aggiungere DefaultIfEmpty ()

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

EF si trasforma correttamente in SQL. Per LINQ agli oggetti è preferibile unirsi a GroupJoin poiché utilizza internamente la funzione di ricerca, ma se si sta eseguendo una query in DB, l’omissione di GroupJoin è AFAIK come performante.

Personlay per me in questo modo è più leggibile rispetto a GroupJoin (). SelectMany ()

Puoi creare un metodo di estensione come:

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

Migliorando la risposta di Ocelot20, se hai un tavolo non ti resta più esterno con cui vuoi solo 0 o 1 file da esso, ma potrebbe avere più, devi ordinare la tua tabella unita:

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

Altrimenti la riga che si ottiene nel join sarà casuale (o più specificatamente, a seconda di quale db si trova per prima cosa).

Trasformando la risposta di Marc Gravell in un metodo di estensione, ho fatto quanto segue.

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

Anche se la risposta accettata funziona ed è utile per Linq to Objects, mi ha infastidito sul fatto che la query SQL non è solo un dritto Outer Join sinistro.

Il codice seguente si basa sul progetto LinkKit che consente di passare espressioni e invocarle alla query.

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

Può essere usato come segue

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