Linq to Entities dołączyć vs groupjoin

182

Przeszukałem sieć, ale wciąż nie mogę znaleźć prostej odpowiedzi. Czy ktoś może wyjaśnić (prostym językiem angielskim), co to GroupJoinjest? Czym różni się od zwykłego wewnętrznego Join? Czy jest powszechnie używany? Czy to tylko dla składni metody? Co ze składnią zapytania? Przykład kodu c # byłby miły.

duyn9uyen
źródło
Według MSDN przyłączenie do grupy jest klauzulą ​​przyłączenia z wyrażeniem do wyrażenia. klauzula Join zawiera więcej informacji i próbki kodu. Zasadniczo jest to sprzężenie wewnętrzne (jeśli żaden element po prawej nie pasuje do żadnego po lewej, wynik jest zerowy); wynik jest jednak podzielony na grupy.
Tim

Odpowiedzi:

375

Zachowanie

Załóżmy, że masz dwie listy:

Id  Value
1   A
2   B
3   C

Id  ChildValue
1   a1
1   a2
1   a3
2   b1
2   b2

Kiedy wy Joindwie listy w Idpolu wynik będzie:

Value ChildValue
A     a1
A     a2
A     a3
B     b1
B     b2

Kiedy wy GroupJoindwie listy w Idpolu wynik będzie:

Value  ChildValues
A      [a1, a2, a3]
B      [b1, b2]
C      []

Tak więc Jointworzy płaski (tabelaryczny) wynik wartości nadrzędnych i podrzędnych.
GroupJointworzy listę wpisów na pierwszej liście, każda z grupą połączonych wpisów na drugiej liście.

Dlatego Joinjest odpowiednikiem INNER JOINSQL: nie ma wpisów dla C. Chociaż GroupJoinjest odpowiednikiem OUTER JOIN: Cznajduje się w zestawie wyników, ale z pustą listą powiązanych pozycji (w zestawie wyników SQL byłby wiersz C - null).

Składnia

Więc niech się dwie listy IEnumerable<Parent>i IEnumerable<Child>odpowiednio. (W przypadku Linq do podmiotów:) IQueryable<T>.

Join składnia byłaby

from p in Parent
join c in Child on p.Id equals c.Id
select new { p.Value, c.ChildValue }

zwracanie IEnumerable<X>gdzie X jest anonimowym typem o dwóch właściwościach, Valuei ChildValue. Ta składnia zapytania używa Joinmetody pod maską.

GroupJoin składnia byłaby

from p in Parent
join c in Child on p.Id equals c.Id into g
select new { Parent = p, Children = g }

zwracanie IEnumerable<Y>gdzie Y jest anonimowym typem składającym się z jednej właściwości typu Parenti właściwości typu IEnumerable<Child>. Ta składnia zapytania używa GroupJoinmetody pod maską.

Moglibyśmy po prostu zrobić select gto drugie zapytanie, które wybrałoby IEnumerable<IEnumerable<Child>>, powiedzmy listę list. W wielu przypadkach wybór z dołączonym rodzicem jest bardziej przydatny.

Niektóre przypadki użycia

1. Wykonanie płaskiego połączenia zewnętrznego.

Jak powiedziano, oświadczenie ...

from p in Parent
join c in Child on p.Id equals c.Id into g
select new { Parent = p, Children = g }

... tworzy listę rodziców z grupami dzieci. Można to zmienić w płaską listę par rodzic-dziecko, dodając dwa małe dodatki:

from p in parents
join c in children on p.Id equals c.Id into g // <= into
from c in g.DefaultIfEmpty()               // <= flattens the groups
select new { Parent = p.Value, Child = c?.ChildValue }

Wynik jest podobny do

Value Child
A     a1
A     a2
A     a3
B     b1
B     b2
C     (null)

Zauważ, że zmienna zakresu c jest ponownie używana w powyższej instrukcji. W ten sposób dowolną joininstrukcję można po prostu przekonwertować na instrukcję outer join, dodając odpowiednik into g from c in g.DefaultIfEmpty()do istniejącej joininstrukcji.

Tutaj świeci składnia zapytania (lub kompleksowa). Metoda (lub płynna) składnia pokazuje, co się naprawdę dzieje, ale trudno jest napisać:

parents.GroupJoin(children, p => p.Id, c => c.Id, (p, c) => new { p, c })
       .SelectMany(x => x.c.DefaultIfEmpty(), (x,c) => new { x.p.Value, c?.ChildValue } )

Tak więc mieszkanie outer joinw LINQ jest GroupJoinspłaszczone o SelectMany.

2. Zachowanie porządku

Załóżmy, że lista rodziców jest nieco dłuższa. Niektóre interfejsy użytkownika tworzą listę wybranych rodziców jako Idwartości w ustalonej kolejności. Użyjmy:

var ids = new[] { 3,7,2,4 };

Teraz wybrani rodzice muszą zostać odfiltrowani z listy rodziców w dokładnie takiej kolejności.

Jeśli zrobimy ...

var result = parents.Where(p => ids.Contains(p.Id));

... kolejność parentsokreśla wynik. Jeśli rodzice zostaną zamówieni przez Id, wynikiem będą rodzice 2, 3, 4, 7. Nie dobrze. Możemy jednak również użyć joindo filtrowania listy. Przy użyciu idsjako pierwszej listy kolejność zostanie zachowana:

from id in ids
join p in parents on id equals p.Id
select p

Wynikiem są rodzice 3, 7, 2, 4.

Gert Arnold
źródło
Czyli w GroupJoin wartości potomne będą zawierać obiekty, które zawierają powiązane wartości?
duyn9uyen
Jak powiedziałeś, GroupJoin jest jak łączenie zewnętrzne, ale ta składnia (czysto linq dla łączenia grup) mówi, że nie jest to łączenie zewnętrzne, ale lewe łączenie zewnętrzne.
Imad,
2
Myślę, że sprecyzowałbym, że „Płaskie łączenie zewnętrzne” to lewe łączenie zewnętrzne.
NetMage,
1
Rozumiem doskonale, teraz rozumiem
peterincumbria
19

Według eduLINQ :

Najlepszym sposobem, aby poradzić sobie z tym, co robi GroupJoin, jest myślenie o dołączeniu. Tam ogólny pomysł polegał na tym, że przejrzeliśmy „zewnętrzną” sekwencję wejściową, znaleźliśmy wszystkie pasujące elementy z „wewnętrznej” sekwencji (w oparciu o rzut kluczowy na każdej sekwencji), a następnie dostarczyliśmy pary pasujących elementów. GroupJoin jest podobny, z tym wyjątkiem, że zamiast dawać pary elementów, daje pojedynczy wynik dla każdego „zewnętrznego” elementu na podstawie tego elementu i sekwencji pasujących „wewnętrznych” elementów .

Jedyną różnicą jest instrukcja return:

Dołącz :

var lookup = inner.ToLookup(innerKeySelector, comparer); 
foreach (var outerElement in outer) 
{ 
    var key = outerKeySelector(outerElement); 
    foreach (var innerElement in lookup[key]) 
    { 
        yield return resultSelector(outerElement, innerElement); 
    } 
} 

Dołącz do grupy :

var lookup = inner.ToLookup(innerKeySelector, comparer); 
foreach (var outerElement in outer) 
{ 
    var key = outerKeySelector(outerElement); 
    yield return resultSelector(outerElement, lookup[key]); 
} 

Przeczytaj więcej tutaj:

MarcinJuraszek
źródło