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.