Linq a Entidades unirse vs unirse a grupo

182

He buscado en la web pero todavía no puedo encontrar una respuesta simple. ¿Alguien puede explicar (en inglés simple) qué GroupJoines? ¿Cómo es diferente de un interior regular Join? ¿Se usa comúnmente? ¿Es solo para la sintaxis del método? ¿Qué pasa con la sintaxis de consulta? Un ejemplo de código C # sería bueno.

duyn9uyen
fuente
Según MSDN, una unión de grupo es una cláusula de unión con una expresión into. La cláusula join tiene más información y ejemplos de código. Es esencialmente una unión interna (si ningún elemento en la derecha coincide con ninguno en la izquierda, se obtiene un resultado nulo); Sin embargo, el resultado se organiza en grupos.
Tim

Respuestas:

375

Comportamiento

Supongamos que tiene dos listas:

Id  Value
1   A
2   B
3   C

Id  ChildValue
1   a1
1   a2
1   a3
2   b1
2   b2

Cuando Joinlas dos listas en el Idcampo el resultado será:

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

Cuando GroupJoinlas dos listas en el Idcampo el resultado será:

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

Entonces Joinproduce un resultado plano (tabular) de valores primarios y secundarios.
GroupJoinproduce una lista de entradas en la primera lista, cada una con un grupo de entradas unidas en la segunda lista.

Por eso Joines el equivalente de INNER JOINen SQL: no hay entradas para C. Mientras GroupJoines el equivalente de OUTER JOIN: Cestá en el conjunto de resultados, pero con una lista vacía de entradas relacionadas (en un conjunto de resultados de SQL habría una fila C - null).

Sintaxis

Entonces, que las dos listas sean IEnumerable<Parent>y IEnumerable<Child>respectivamente. (En caso de Linq a Entidades:) IQueryable<T>.

Join la sintaxis sería

from p in Parent
join c in Child on p.Id equals c.Id
select new { p.Value, c.ChildValue }

devolviendo un IEnumerable<X>donde X es un tipo anónimo con dos propiedades, Valuey ChildValue. Esta sintaxis de consulta utiliza el Joinmétodo bajo el capó.

GroupJoin la sintaxis sería

from p in Parent
join c in Child on p.Id equals c.Id into g
select new { Parent = p, Children = g }

devolviendo un IEnumerable<Y>donde Y es un tipo anónimo que consiste en una propiedad de tipo Parenty una propiedad de tipo IEnumerable<Child>. Esta sintaxis de consulta utiliza el GroupJoinmétodo bajo el capó.

Podríamos hacer select gen la última consulta, que seleccionaría una IEnumerable<IEnumerable<Child>>, digamos una lista de listas. En muchos casos, la selección con el padre incluido es más útil.

Algunos casos de uso

1. Produciendo una unión externa plana.

Como se dijo, la declaración ...

from p in Parent
join c in Child on p.Id equals c.Id into g
select new { Parent = p, Children = g }

... produce una lista de padres con grupos secundarios. Esto se puede convertir en una lista plana de pares padre-hijo mediante dos pequeñas adiciones:

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 }

El resultado es similar a

Value Child
A     a1
A     a2
A     a3
B     b1
B     b2
C     (null)

Tenga en cuenta que la variable de rango c se reutiliza en la declaración anterior. Al hacer esto, cualquier joindeclaración simplemente se puede convertir en una outer joinagregando el equivalente de into g from c in g.DefaultIfEmpty()una joindeclaración existente .

Aquí es donde brilla la sintaxis de consulta (o integral). La sintaxis del método (o fluido) muestra lo que realmente sucede, pero es difícil de escribir:

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

Entonces, un piso outer joinen LINQ es GroupJoin, aplanado por SelectMany.

2. Preservar el orden

Supongamos que la lista de padres es un poco más larga. Algunas IU producen una lista de padres seleccionados como Idvalores en un orden fijo. Vamos a usar:

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

Ahora los padres seleccionados deben filtrarse de la lista de padres en este orden exacto.

Si lo hacemos ...

var result = parents.Where(p => ids.Contains(p.Id));

... el orden de parentsdeterminará el resultado. Si los padres son ordenados por Id, el resultado será padres 2, 3, 4, 7. No es bueno. Sin embargo, también podemos usar joinpara filtrar la lista. Y al usar idscomo primera lista, se preservará el orden:

from id in ids
join p in parents on id equals p.Id
select p

El resultado son los padres 3, 7, 2, 4.

Gert Arnold
fuente
Entonces, en un GroupJoin, los valores secundarios contendrán objetos, que contienen los valores relacionados.
duyn9uyen
Como dijiste, GroupJoin es como una combinación externa, pero esa sintaxis (puramente linq para combinación de grupo) dice que no es como una combinación externa sino una combinación externa izquierda.
Imad
2
Creo que especificaría que la "Unión externa plana" es una Unión externa izquierda.
NetMage
1
Explicado perfectamente, ahora entiendo
peterincumbria
19

De acuerdo con eduLINQ :

La mejor manera de entender lo que hace GroupJoin es pensar en Join. Allí, la idea general fue que examinamos la secuencia de entrada "externa", encontramos todos los elementos coincidentes de la secuencia "interna" (basada en una proyección clave en cada secuencia) y luego produjimos pares de elementos coincidentes. GroupJoin es similar, excepto que en lugar de producir pares de elementos, produce un único resultado para cada elemento "externo" basado en ese elemento y la secuencia de elementos "internos" coincidentes .

La única diferencia está en la declaración de devolución:

Unirse :

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

Leer más aquí:

MarcinJuraszek
fuente