2009-02-25 15 views
181

Zakładając mam lewe sprzężenie zewnętrzne, takie jak:Jak wykonać lewe sprzężenie zewnętrzne za pomocą metod rozszerzenie 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 } 

Jak chciałbym wyrazić to samo zadanie za pomocą metody rozszerzenie? Na przykład.

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

Po prostu nie mogę się oprzeć! nazwa .... @LaserJesus jego sooo +1 – BozoJoe

Odpowiedz

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

Panie, pomóż nam ... –

+12

To właściwie nie jest tak szalone, jak się wydaje. Zasadniczo 'GroupJoin' wykonuje lewe sprzężenie zewnętrzne, część' SelectMany' jest potrzebna tylko w zależności od tego, co chcesz wybrać. –

+5

Ten wzorzec jest świetny, ponieważ Entity Framework rozpoznaje go jako lewe połączenie, które uważałem za niemożliwość. –

14

Można utworzyć metodę rozszerzenia jak:

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

Wygląda na to, że zadziała (na moje życzenie). Czy możesz podać przykład? Jestem nowy w LINQ Extensions i mam problemy z zawijaniem głowy wokół tej sytuacji Left Join Jestem w ... – Shiva

+0

wyrzuca wyjątek mówiąc, że nie może przekonwertować polecenia Invoke – Skychan

+0

@Skychan Może być muszę na to popatrzeć, to stara odpowiedź i pracował w tym czasie. Z którego ramienia korzystasz? Mam na myśli wersję .NET? – hajirazin

66

Ponieważ wydaje się być de facto SO pytanie dla lewego sprzężenia zewnętrzne za pomocą metody (rozbudowa) składni, myślałem, że dodaj alternatywę do aktualnie wybranej odpowiedzi, która (przynajmniej w moim odczuciu) była bardziej powszechna, co mam za sobą.Aby wyświetlić różnicę za pomocą prostego zestawu danych (zakładając, jesteśmy łączenia na samych wartościach):

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 }  

opcja 2 jest wierny typowej lewe sprzężenie zewnętrzne definicję, ale jak już wspomniałem jest często niepotrzebnie skomplikowane w zależności od zestawu danych.

+6

Myślę, że "bs.SingleOrDefault()" nie będzie działać, jeśli masz następującą opcję Dołącz lub Dołącz. Potrzebujemy "bs.FirstOrDefault()" w tych przypadkach. – Dherik

+2

Prawda, Entity Framework i Linq to SQL wymagają tego, ponieważ nie mogą one łatwo wykonać sprawdzenia 'Single' w trakcie łączenia. 'SingleOrDefault' jest jednak bardziej" poprawnym "sposobem na wykazanie tego IMO. – Ocelot20

+1

Należy pamiętać o zamawianiu połączonej tabeli lub funkcja .FirstOrDefault() pobiera losowy wiersz z wielu wierszy, które mogą być zgodne z kryteriami łączenia, niezależnie od tego, co znajduje się w bazie danych. –

19

Metoda łączenia grup jest niepotrzebna, aby uzyskać połączenie dwóch zestawów danych.

Inner Join:

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

Dla LEFT JOIN tylko dodać DefaultIfEmpty()

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

EF poprawnie przekształca do SQL. W przypadku LINQ do obiektów lepiej jest dołączyć przy użyciu GroupJoin, ponieważ wewnętrznie korzysta on z Lookup, ale jeśli kwerendujesz na DB, pomijanie GroupJoin jest AFAIK jako performant.

Personlay dla mnie ten sposób jest bardziej czytelny w porównaniu do GroupJoin(). SelectMany()

+0

Tak, mniej kodu samego wyniku. Dzięki. –

4

Poprawa na odpowiedź Ocelot20, o ile masz stolik jesteś w lewo zewnętrzna łączenia z którym chcesz tylko 0 lub 1 rzędy z niego, ale może mieć wiele, trzeba zamówić dołączył tabeli:

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

Inaczej który wiersz można uzyskać w sprzężeniu będzie losowe (lub dokładniej, w zależności db stanie znaleźć pierwszy).

+0

To wszystko! Każda niegrzeczna relacja jeden do jednego. – it3xl

Powiązane problemy