2009-02-25 21 views
181

Supponendo che ho un join esterno sinistro in quanto tale:Come si esegue un join esterno sinistro utilizzando metodi di estensione LINQ

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 dovrei esprimere la stessa operazione utilizzando metodi di estensione? Per esempio.

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

Non riesco proprio a resistere! il nome .... @LaserJesus è mooolto +1 – BozoJoe

risposta

291
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}); 
+218

Signore aiutaci ... –

+12

Questo in realtà non è così folle come sembra. Fondamentalmente 'GroupJoin' fa il join esterno sinistro, la parte' SelectMany' è necessaria solo in base a ciò che si desidera selezionare. –

+5

Questo modello è eccezionale perché Entity Framework lo riconosce come Join sinistro, che credevo fosse impossibile. –

14

È possibile creare metodo di estensione come:

public static IEnumerable<TResult> LeftOuterJoin<TSource, TInner, TKey, TResult>(this IEnumerable<TSource> source, IEnumerable<TInner> other, Func<TSource, TKey> func, Func<TInner, TKey> innerkey, Func<TSource, TInner, TResult> 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); 
    } 
+0

Sembra che funzioni (per le mie esigenze). Puoi fornire un esempio? Sono nuovo di LINQ Extensions e sto avendo difficoltà a girare la testa in questa situazione di Join di sinistra in cui sono ... – Shiva

+0

lancia un'eccezione dicendo impossibile convertire il comando Invoke – Skychan

+0

@Skychan Potrebbe essere necessario guardarlo, è una vecchia risposta e stava lavorando in quel momento. Quale Framework stai usando? Intendo la versione .NET? – hajirazin

66

Dal momento che questo sembra essere il fatto domanda SO per esterno sinistro si unisce con il metodo (estensione) della sintassi, ho pensato che sarebbe aggiungi un'alternativa alla risposta attualmente selezionata che (almeno nella mia esperienza) è stata più comunemente quella che sto cercando

// 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 usando un set di dati semplice (assumendo che ci stiamo unendo i valori stessi):

List<int> tableA = new List<int> { 1, 2, 3 }; 
List<int?> tableB = new List<int?> { 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<int> tableA = new List<int> { 1, 2, 3 }; 
List<int?> tableB = new List<int?> { 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 }  

Opzione 2 è fedele alla tipica join esterno sinistro definizione, ma come ho detto prima è spesso inutilmente complesse a seconda del set di dati.

+6

Penso che "bs.SingleOrDefault()" non funzioni se si ha un altro Join o Includi. In questi casi abbiamo bisogno del "bs.FirstOrDefault()". – Dherik

+2

True, Entity Framework e Linq to SQL richiedono entrambi, dal momento che non possono facilmente eseguire il controllo 'Single' in un join. 'SingleOrDefault' è tuttavia un modo più" corretto "per dimostrare questo IMO. – Ocelot20

+1

È necessario ricordare di ordinare la tabella unita o .FirstOrDefault() otterrà una riga casuale dalle più righe che potrebbero corrispondere ai criteri di join, qualunque sia il risultato che il database trova prima. –

19

Non è necessario unire un metodo per unire due insiemi di dati.

Inner Join:

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

Per LEFT JOIN Just Add DefaultIfEmpty()

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

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

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

+0

Sì, meno codice stesso risultato. Grazie. –

4

migliorando la risposta di Ocelot20, se si dispone di una tabella si è lasciato esterno unisce con cui si desidera solo 0 o 1 righe fuori di esso, ma potrebbe avere più, è necessario ordinare la vostra 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 quale riga si ottiene nel join sta per essere casuale (o più precisamente, a seconda di quale il db accade di trovare prima).

+0

Questo è tutto! Qualunque relazione uno a uno non garantita. – it3xl

Problemi correlati