Comportement
Supposons que vous ayez deux listes:
Id Value
1 A
2 B
3 C
Id ChildValue
1 a1
1 a2
1 a3
2 b1
2 b2
Lorsque vous Joinles deux listes sur le Idterrain, le résultat sera:
Value ChildValue
A a1
A a2
A a3
B b1
B b2
Lorsque vous GroupJoinles deux listes sur le Idterrain, le résultat sera:
Value ChildValues
A [a1, a2, a3]
B [b1, b2]
C []
Produit donc Joinun résultat plat (tabulaire) des valeurs parent et enfant.
GroupJoinproduit une liste d'entrées dans la première liste, chacune avec un groupe d'entrées jointes dans la seconde liste.
C'est pourquoi Joinest l'équivalent de INNER JOINSQL: il n'y a pas d'entrées pour C. While GroupJoinest l'équivalent de OUTER JOIN: Cest dans le jeu de résultats, mais avec une liste vide d'entrées associées (dans un jeu de résultats SQL, il y aurait une ligne C - null).
Syntaxe
Alors laissez les deux listes être IEnumerable<Parent>et IEnumerable<Child>respectivement. (Dans le cas de Linq aux entités:) IQueryable<T>.
Join la syntaxe serait
from p in Parent
join c in Child on p.Id equals c.Id
select new { p.Value, c.ChildValue }
renvoyer un IEnumerable<X>où X est un type anonyme avec deux propriétés, Valueet ChildValue. Cette syntaxe de requête utilise la Joinméthode sous le capot.
GroupJoin la syntaxe serait
from p in Parent
join c in Child on p.Id equals c.Id into g
select new { Parent = p, Children = g }
renvoyant un IEnumerable<Y>où Y est un type anonyme composé d'une propriété de type Parentet d'une propriété de type IEnumerable<Child>. Cette syntaxe de requête utilise la GroupJoinméthode sous le capot.
Nous pourrions simplement faire select gdans la dernière requête, qui sélectionnerait une IEnumerable<IEnumerable<Child>>, disons une liste de listes. Dans de nombreux cas, la sélection avec le parent inclus est plus utile.
Quelques cas d'utilisation
1. Produire une jointure extérieure plate.
Comme dit, la déclaration ...
from p in Parent
join c in Child on p.Id equals c.Id into g
select new { Parent = p, Children = g }
... produit une liste de parents avec des groupes d'enfants. Cela peut être transformé en une liste plate de paires parent-enfant par deux petits ajouts:
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 }
Le résultat est similaire à
Value Child
A a1
A a2
A a3
B b1
B b2
C (null)
Notez que la variable de plage c est réutilisée dans l'instruction ci-dessus. En faisant cela, toute joininstruction peut simplement être convertie en un outer joinen ajoutant l'équivalent de into g from c in g.DefaultIfEmpty()à une joininstruction existante .
C'est là que brille la syntaxe de requête (ou complète). La syntaxe de méthode (ou couramment) montre ce qui se passe réellement, mais c'est difficile à écrire:
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 } )
Donc, un appartement outer joindans LINQ est un GroupJoin, aplati par SelectMany.
2. Maintenir l'ordre
Supposons que la liste des parents soit un peu plus longue. Certaines interfaces utilisateur produisent une liste de parents sélectionnés sous forme de Idvaleurs dans un ordre fixe. Utilisons:
var ids = new[] { 3,7,2,4 };
Désormais, les parents sélectionnés doivent être filtrés de la liste des parents dans cet ordre exact.
Si nous faisons ...
var result = parents.Where(p => ids.Contains(p.Id));
... l'ordre de parentsdéterminera le résultat. Si les parents sont commandés par Id, le résultat sera les parents 2, 3, 4, 7. Pas bien. Cependant, nous pouvons également utiliser joinpour filtrer la liste. Et en utilisant idscomme première liste, l'ordre sera conservé:
from id in ids
join p in parents on id equals p.Id
select p
Le résultat est les parents 3, 7, 2, 4.