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 Joinle due liste sul Idcampo il risultato sarà:
Value ChildValue
A a1
A a2
A a3
B b1
B b2
Quando GroupJoinle due liste sul Idcampo il risultato sarà:
Value ChildValues
A [a1, a2, a3]
B [b1, b2]
C []
Quindi Joinproduce un risultato piatto (tabulare) dei valori padre e figlio.
GroupJoinproduce un elenco di voci nel primo elenco, ognuna con un gruppo di voci unite nel secondo elenco.
Ecco perché Joinè l'equivalente di INNER JOINin 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à Valuee ChildValue. Questa sintassi della query utilizza il Joinmetodo 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 Parente una proprietà di tipo IEnumerable<Child>. Questa sintassi della query utilizza il GroupJoinmetodo sottostante.
Potremmo fare solo select gin 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 joinistruzione può essere semplicemente convertita in an outer joinaggiungendo 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 joinin 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 Idvalori 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 parentsdeterminerà 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 joinper filtrare l'elenco. E usando idscome 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.