Linq to Entities join vs groupjoin

Eu pesquisei na web, mas ainda não consigo encontrar uma resposta simples. Alguém pode explicar (em inglês simples) o que é um GroupJoin ? Como é diferente de uma Join interna regular? É comumente usado? É apenas para a syntax do método? E quanto a syntax de consulta? Um exemplo de código c # seria legal.

Comportamento

Suponha que você tenha duas listas:

 Id Value 1 A 2 B 3 C Id ChildValue 1 a1 1 a2 1 a3 2 b1 2 b2 

Quando você Join as duas listas no campo Id , o resultado será:

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

Quando você GroupJoin as duas listas no campo Id , o resultado será:

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

So Join produz um resultado plano (tabular) de valores pai e filho.
GroupJoin produz uma lista de inputs na primeira lista, cada uma com um grupo de inputs unidas na segunda lista.

É por isso que Join é o equivalente de INNER JOIN no SQL: não há inputs para C Enquanto GroupJoin é o equivalente de OUTER JOIN : C está no conjunto de resultados, mas com uma lista vazia de inputs relacionadas (em um conjunto de resultados SQL, haveria uma linha C - null ).

Sintaxe

Então, deixe as duas listas serem IEnumerable e IEnumerable respectivamente. (No caso de Linq to Entities: IQueryable ).

Join syntax de Join seria

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

retornando um IEnumerable onde X é um tipo anônimo com duas propriedades, Value e ChildValue . Essa syntax de consulta usa o método Join sob o capô.

GroupJoin syntax do GroupJoin seria

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

retornando um IEnumerable onde Y é um tipo anônimo que consiste em uma propriedade do tipo Parent e uma propriedade do tipo IEnumerable . Essa syntax de consulta usa o método GroupJoin sob o capô.

Poderíamos apenas select g na última consulta, que selecionaria um IEnumerable> , digamos uma lista de listas. Em muitos casos, o select com o pai incluído é mais útil.

Alguns casos de uso

1. Produzir uma junit externa plana.

Como disse, a declaração …

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

… produz uma lista de pais com grupos de filhos. Isso pode ser transformado em uma lista simples de pares pai-filho por duas pequenas adições:

 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 } 

O resultado é semelhante ao

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

Observe que a variável de intervalo c é reutilizada na declaração acima. Fazendo isso, qualquer instrução de join pode simplesmente ser convertida em uma outer join , adicionando o equivalente into g from c in g.DefaultIfEmpty() a uma instrução de join existente.

É aqui que a syntax da consulta (ou abrangente) se destaca. A syntax do método (ou fluente) mostra o que realmente acontece, mas é difícil escrever:

 parents.GroupJoin(children, p => p.Id, c => c.Id, (p, c) => new { p, c }) .SelectMany(x => xcDefaultIfEmpty(), (x,c) => new { xpValue, c?.ChildValue } ) 

Portanto, uma outer join plana no LINQ é um GroupJoin , achatada por SelectMany .

2. ordem de preservação

Suponha que a lista de pais seja um pouco mais longa. Alguns UI produz uma lista de pais selecionados como valores de Id em uma ordem fixa. Vamos usar:

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

Agora os pais selecionados devem ser filtrados da lista de pais nesta ordem exata.

Se nós fizermos ...

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

... a ordem dos parents determinará o resultado. Se os pais forem ordenados por Id , o resultado será os pais 2, 3, 4, 7. Não é bom. No entanto, também podemos usar join para filtrar a lista. E usando ids como primeira lista, o pedido será preservado:

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

O resultado é os pais 3, 7, 2, 4.

De acordo com o eduLINQ :

A melhor maneira de entender o que o GroupJoin faz é pensar em Join. Lá, a ideia geral era que nós examinamos a sequência de input “externa”, encontramos todos os itens correspondentes da sequência “interna” (com base em uma projeção de chave em cada sequência) e, em seguida, produzimos pares de elementos correspondentes. O GroupJoin é semelhante, exceto que, em vez de gerar pares de elementos, ele produz um único resultado para cada item “externo” com base nesse item e na sequência de itens “internos” correspondentes .

A única diferença está na declaração de retorno:

Juntese :

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

Leia mais aqui:

  • Reimplementando o LINQ para Objetos: Parte 19 – Join

  • Reimplementando o LINQ para Objetos: Parte 22 – GroupJoin