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 Join
las dos listas en el Id
campo el resultado será:
Value ChildValue
A a1
A a2
A a3
B b1
B b2
Cuando GroupJoin
las dos listas en el Id
campo el resultado será:
Value ChildValues
A [a1, a2, a3]
B [b1, b2]
C []
Entonces Join
produce un resultado plano (tabular) de valores primarios y secundarios.
GroupJoin
produce una lista de entradas en la primera lista, cada una con un grupo de entradas unidas en la segunda lista.
Por eso Join
es el equivalente de INNER JOIN
en SQL: no hay entradas para C
. Mientras GroupJoin
es el equivalente de OUTER JOIN
: C
está 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, Value
y ChildValue
. Esta sintaxis de consulta utiliza el Join
mé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 Parent
y una propiedad de tipo IEnumerable<Child>
. Esta sintaxis de consulta utiliza el GroupJoin
método bajo el capó.
Podríamos hacer select g
en 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 join
declaración simplemente se puede convertir en una outer join
agregando el equivalente de into g from c in g.DefaultIfEmpty()
una join
declaració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 join
en 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 Id
valores 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 parents
determinará 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 join
para filtrar la lista. Y al usar ids
como 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.