Comportamento
Supponiamo di avere due elenchi:
Id Value
1 A
2 B
3 C
Id ChildValue
1 a1
1 a2
1 a3
2 b1
2 b2
Quando Join
le due liste sul Id
campo il risultato sarà:
Value ChildValue
A a1
A a2
A a3
B b1
B b2
Quando GroupJoin
le due liste sul Id
campo il risultato sarà:
Value ChildValues
A [a1, a2, a3]
B [b1, b2]
C []
Quindi Join
produce un risultato piatto (tabulare) dei valori padre e figlio.
GroupJoin
produce un elenco di voci nel primo elenco, ognuna con un gruppo di voci unite nel secondo elenco.
Ecco perché Join
è l'equivalente di INNER JOIN
in SQL: non ci sono voci per C
. While GroupJoin
è l'equivalente di OUTER JOIN
: C
è nel set di risultati, ma con un elenco vuoto di voci correlate (in un set di risultati SQL ci sarebbe una riga C - null
).
Sintassi
Quindi lascia che le due liste siano IEnumerable<Parent>
e IEnumerable<Child>
rispettivamente. (Nel caso di Linq alle Entità:) IQueryable<T>
.
Join
la sintassi sarebbe
from p in Parent
join c in Child on p.Id equals c.Id
select new { p.Value, c.ChildValue }
restituendo un IEnumerable<X>
dove X è un tipo anonimo con due proprietà Value
e ChildValue
. Questa sintassi della query utilizza il Join
metodo sottostante.
GroupJoin
la sintassi sarebbe
from p in Parent
join c in Child on p.Id equals c.Id into g
select new { Parent = p, Children = g }
restituendo un IEnumerable<Y>
dove Y è un tipo anonimo costituito da una proprietà di tipo Parent
e una proprietà di tipo IEnumerable<Child>
. Questa sintassi della query utilizza il GroupJoin
metodo sottostante.
Potremmo fare solo select g
in quest'ultima query, che selezionerebbe un IEnumerable<IEnumerable<Child>>
, diciamo un elenco di elenchi. In molti casi è più utile selezionare con il genitore incluso.
Alcuni casi d'uso
1. Produzione di un join esterno piatto.
Come detto, la dichiarazione ...
from p in Parent
join c in Child on p.Id equals c.Id into g
select new { Parent = p, Children = g }
... produce un elenco di genitori con gruppi di bambini. Questo può essere trasformato in un elenco semplice di coppie genitore-figlio con due piccole aggiunte:
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 }
Il risultato è simile a
Value Child
A a1
A a2
A a3
B b1
B b2
C (null)
Si noti che la variabile intervallo c
viene riutilizzata nell'istruzione precedente. In questo modo, qualsiasi join
istruzione può essere semplicemente convertita in an outer join
aggiungendo l'equivalente di into g from c in g.DefaultIfEmpty()
a un'istruzione esistente join
.
Qui è dove brilla la sintassi della query (o completa). La sintassi del metodo (o fluente) mostra cosa succede realmente, ma è difficile scrivere:
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 } )
Quindi un appartamento outer join
in LINQ è un GroupJoin
, appiattito da SelectMany
.
2. Ordine di conservazione
Supponiamo che l'elenco dei genitori sia un po 'più lungo. Alcune UI producono un elenco di genitori selezionati come Id
valori in un ordine fisso. Usiamo:
var ids = new[] { 3,7,2,4 };
Ora i genitori selezionati devono essere filtrati dall'elenco dei genitori in questo preciso ordine.
Se lo facciamo ...
var result = parents.Where(p => ids.Contains(p.Id));
... l'ordine di parents
determinerà il risultato. Se i genitori sono ordinati per Id
, il risultato saranno i genitori 2, 3, 4, 7. Non va bene. Tuttavia, possiamo anche utilizzare join
per filtrare l'elenco. E usando ids
come primo elenco, l'ordine verrà preservato:
from id in ids
join p in parents on id equals p.Id
select p
Il risultato sono i genitori 3, 7, 2, 4.