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 Join
les deux listes sur le Id
terrain, le résultat sera:
Value ChildValue
A a1
A a2
A a3
B b1
B b2
Lorsque vous GroupJoin
les deux listes sur le Id
terrain, le résultat sera:
Value ChildValues
A [a1, a2, a3]
B [b1, b2]
C []
Produit donc Join
un résultat plat (tabulaire) des valeurs parent et enfant.
GroupJoin
produit 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 Join
est l'équivalent de INNER JOIN
SQL: il n'y a pas d'entrées pour C
. While GroupJoin
est l'équivalent de OUTER JOIN
: C
est 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, Value
et ChildValue
. Cette syntaxe de requête utilise la Join
mé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 Parent
et d'une propriété de type IEnumerable<Child>
. Cette syntaxe de requête utilise la GroupJoin
méthode sous le capot.
Nous pourrions simplement faire select g
dans 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 join
instruction peut simplement être convertie en un outer join
en ajoutant l'équivalent de into g from c in g.DefaultIfEmpty()
à une join
instruction 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 join
dans 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 Id
valeurs 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 parents
dé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 join
pour filtrer la liste. Et en utilisant ids
comme 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.