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 Join
dwie listy w Id
polu wynik będzie:
Value ChildValue
A a1
A a2
A a3
B b1
B b2
Kiedy wy GroupJoin
dwie listy w Id
polu wynik będzie:
Value ChildValues
A [a1, a2, a3]
B [b1, b2]
C []
Tak więc Join
tworzy płaski (tabelaryczny) wynik wartości nadrzędnych i podrzędnych.
GroupJoin
tworzy listę wpisów na pierwszej liście, każda z grupą połączonych wpisów na drugiej liście.
Dlatego Join
jest odpowiednikiem INNER JOIN
SQL: nie ma wpisów dla C
. Chociaż GroupJoin
jest odpowiednikiem OUTER JOIN
: C
znajduje 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, Value
i ChildValue
. Ta składnia zapytania używa Join
metody 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 Parent
i właściwości typu IEnumerable<Child>
. Ta składnia zapytania używa GroupJoin
metody pod maską.
Moglibyśmy po prostu zrobić select g
to 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ą join
instrukcję można po prostu przekonwertować na instrukcję outer join
, dodając odpowiednik into g from c in g.DefaultIfEmpty()
do istniejącej join
instrukcji.
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 join
w LINQ jest GroupJoin
spł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 Id
wartoś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ść parents
okreś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ć join
do filtrowania listy. Przy użyciu ids
jako 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.