Linq to Entities join vs groupjoin

182

J'ai effectué une recherche sur le Web, mais je ne trouve toujours pas de réponse simple. Quelqu'un peut-il expliquer (en anglais simple) ce qu'est un GroupJoin? En quoi est-ce différent d'un intérieur ordinaire Join? Est-il couramment utilisé? Est-ce uniquement pour la syntaxe de méthode? Qu'en est-il de la syntaxe des requêtes? Un exemple de code c # serait bien.

duyn9uyen
la source
Selon MSDN, une jointure de groupe est une clause de jointure avec une expression into. La clause join contient plus d'informations et des exemples de code. C'est essentiellement une jointure interne (si aucun élément de la droite ne correspond à celui de la gauche, vous obtenez un résultat nul); cependant le résultat est organisé en groupes.
Tim

Réponses:

375

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.

Gert Arnold
la source
Donc, dans un GroupJoin, les valeurs enfants contiendront des objets, qui contiennent les valeurs associées?
duyn9uyen
Comme vous l'avez dit, GroupJoin est comme une jointure externe, mais cette syntaxe (purement linq pour la jointure de groupe) dit que ce n'est pas comme une jointure externe mais une jointure externe gauche.
Imad
2
Je pense que je préciserais que la "jointure externe plate" est une jointure externe gauche.
NetMage
1
Expliqué parfaitement, je comprends maintenant
peterincumbria
19

Selon eduLINQ :

La meilleure façon de comprendre ce que fait GroupJoin est de penser à Join. Là, l'idée générale était que nous avons regardé à travers la séquence d'entrée "externe", trouvé tous les éléments correspondants de la séquence "interne" (basée sur une projection clé sur chaque séquence) et ensuite produit des paires d'éléments correspondants. GroupJoin est similaire, sauf qu'au lieu de produire des paires d'éléments, il donne un seul résultat pour chaque élément "externe" basé sur cet élément et la séquence des éléments "internes" correspondants .

La seule différence est dans l'instruction de retour:

Rejoindre :

var lookup = inner.ToLookup(innerKeySelector, comparer); 
foreach (var outerElement in outer) 
{ 
    var key = outerKeySelector(outerElement); 
    foreach (var innerElement in lookup[key]) 
    { 
        yield return resultSelector(outerElement, innerElement); 
    } 
} 

GroupJoin :

var lookup = inner.ToLookup(innerKeySelector, comparer); 
foreach (var outerElement in outer) 
{ 
    var key = outerKeySelector(outerElement); 
    yield return resultSelector(outerElement, lookup[key]); 
} 

En savoir plus ici:

MarcinJuraszek
la source