web-dev-qa-db-fra.com

Linq aux entités rejoindre vs groupjoin

J'ai fait une recherche sur le Web mais je n'arrive toujours pas à trouver une réponse simple. Quelqu'un peut-il s'il vous plaît expliquer (en anglais simple) ce qu'est un GroupJoin? En quoi est-il différent d'un Join interne normal? Est-il couramment utilisé? Est-ce seulement pour la syntaxe de la méthode? Qu'en est-il de la syntaxe de requête? Un exemple de code c # serait Nice.

157
duyn9uyen

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 du champ Id, le résultat sera:

Value ChildValue
A     a1
A     a2
A     a3
B     b1
B     b2

Lorsque vous GroupJoin les deux listes du champ Id, le résultat sera:

Value  ChildValues
A      [a1, a2, a3]
B      [b1, b2]
C      []

Donc, Join produit 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 deuxième liste.

C'est pourquoi Join est l'équivalent de INNER JOIN en SQL: il n'y a pas d'entrées pour C. Alors que GroupJoin équivaut à OUTER JOIN: C figure 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 to Entities: 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 méthode Join 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 }

renvoyer 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 méthode GroupJoin sous le capot.

Nous pourrions simplement faire select g dans la dernière requête, ce qui sélectionnerait un IEnumerable<IEnumerable<Child>>, par exemple 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 externe 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. Ainsi, toute instruction join peut simplement être convertie en un outer join en ajoutant l’équivalent de into g from c in g.DefaultIfEmpty() à une instruction join existante.

C'est là que la syntaxe de requête (ou complète) brille. La syntaxe Method (ou fluent) montre ce qui se passe réellement, mais il est difficile d'é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 } )

Ainsi, un outer join plat de LINQ est un GroupJoin, aplati par SelectMany.

2. Préserver l'ordre

Supposons que la liste des parents est un peu plus longue. Certaines interfaces utilisateur génèrent une liste des parents sélectionnés sous forme de valeurs Id dans un ordre fixe. Utilisons:

var ids = new[] { 3,7,2,4 };

Maintenant, 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 préservé:

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.

331
Gert Arnold

Selon eduLINQ :

Le meilleur moyen de comprendre ce que fait GroupJoin est de penser à Join. Là, l’idée générale était que nous avons examiné la séquence de saisie "externe", trouvé tous les éléments correspondants de la séquence "interne" (sur la base d’une projection de clé sur chaque séquence), puis produit des paires d’éléments correspondants. GroupJoin est similaire, sauf qu'au lieu de générer des paires d'éléments, il génère un seul résultat pour chaque élément "externe" en fonction de cet élément et de la séquence d'éléments "internes" correspondants .

La seule différence est dans l'instruction return:

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]); 
} 

Lire la suite ici:

17
MarcinJuraszek