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.