LINQ - Join esquerdo, agrupar por e contar

166

Digamos que eu tenho esse SQL:

SELECT p.ParentId, COUNT(c.ChildId)
FROM ParentTable p
  LEFT OUTER JOIN ChildTable c ON p.ParentId = c.ChildParentId
GROUP BY p.ParentId

Como posso traduzir isso em LINQ to SQL? Fiquei preso na COUNT (c.ChildId), o SQL gerado sempre parece gerar COUNT (*). Aqui está o que eu tenho até agora:

from p in context.ParentTable
join c in context.ChildTable on p.ParentId equals c.ChildParentId into j1
from j2 in j1.DefaultIfEmpty()
group j2 by p.ParentId into grouped
select new { ParentId = grouped.Key, Count = grouped.Count() }

Obrigado!

pbz
fonte

Respostas:

189
from p in context.ParentTable
join c in context.ChildTable on p.ParentId equals c.ChildParentId into j1
from j2 in j1.DefaultIfEmpty()
group j2 by p.ParentId into grouped
select new { ParentId = grouped.Key, Count = grouped.Count(t=>t.ChildId != null) }
Mehrdad Afshari
fonte
OK, isso funciona, mas por quê? Como você pensa sobre isso? Como a contagem de valores nulos não nos dá o mesmo que COUNT (c.ChildId)? Obrigado.
28409 pbz
4
É assim que o SQL funciona. COUNT (nome do campo) contará as linhas desse campo que não são nulas. Talvez eu não receba sua pergunta, por favor, esclareça se é esse o caso.
Mehrdad Afshari 29/03/09
Acho que sempre pensei nisso em termos de contagem de linhas, mas você está correto, apenas os valores não nulos são contados. Obrigado.
28409 pbz
1
.Count () gerará COUNT (*) que contará todas as linhas nesse grupo, a propósito.
Mehrdad Afshari
Eu tive exatamente o mesmo problema, no entanto, comparar t => t.ChildID! = Null não funcionou para mim. O resultado sempre foi um objeto nulo e o Resharper reclamou que a expressão sempre era verdadeira. Então eu usei (t => t! = Null) e isso funcionou para mim.
Joe
55

Considere usar uma subconsulta:

from p in context.ParentTable 
let cCount =
(
  from c in context.ChildTable
  where p.ParentId == c.ChildParentId
  select c
).Count()
select new { ParentId = p.Key, Count = cCount } ;

Se os tipos de consulta estiverem conectados por uma associação, isso simplifica para:

from p in context.ParentTable 
let cCount = p.Children.Count()
select new { ParentId = p.Key, Count = cCount } ;
Amy B
fonte
Se bem me lembro (já faz um tempo), essa consulta era uma versão simplificada de uma grande. Se tudo que eu precisasse fosse a chave e conte sua solução, seria melhor / mais limpa.
Pbz 09/07/19
1
Seu comentário não faz sentido no contexto da pergunta original e das respostas aprovadas. Além disso - se você quiser mais do que a chave, terá toda a linha pai para desenhar.
Amy B
A solução com a letpalavra - chave gerará uma subconsulta igual à solução ingressada no grupo @Mosh.
Mohsen Afshin
@MohsenAfshin sim, ele gera uma subconsulta igual à consulta com uma subconsulta na minha resposta diretamente acima.
Amy B
39

RESPOSTA ATRASADA:

Você não precisa da junção esquerda se tudo o que está fazendo é Count (). Observe que, join...intona verdade, é traduzido para o GroupJoinqual retorna agrupamentos como new{parent,IEnumerable<child>}esse; você só precisa chamar Count()o grupo:

from p in context.ParentTable
join c in context.ChildTable on p.ParentId equals c.ChildParentId into g
select new { ParentId = p.Id, Count = g.Count() }

Na sintaxe do método de extensão, a join intoé equivalente a GroupJoin(enquanto a joinsem um intoé Join):

context.ParentTable
    .GroupJoin(
                   inner: context.ChildTable
        outerKeySelector: parent => parent.ParentId,
        innerKeySelector: child => child.ParentId,
          resultSelector: (parent, children) => new { parent.Id, Count = children.Count() }
    );
Eren Ersönmez
fonte
8

Embora a idéia por trás da sintaxe do LINQ seja emular a sintaxe do SQL, você não deve sempre pensar em traduzir diretamente seu código SQL no LINQ. Nesse caso em particular, não precisamos nos agrupar, já que ingressar é um ingresso em grupo.

Aqui está a minha solução:

from p in context.ParentTable
join c in context.ChildTable on p.ParentId equals c.ChildParentId into joined
select new { ParentId = p.ParentId, Count = joined.Count() }

Diferentemente da solução mais votada aqui, não precisamos das verificações j1 , j2 e nula no Count (t => t.ChildId! = Null)

Mosh
fonte
7
 (from p in context.ParentTable     
  join c in context.ChildTable 
    on p.ParentId equals c.ChildParentId into j1 
  from j2 in j1.DefaultIfEmpty() 
     select new { 
          ParentId = p.ParentId,
         ChildId = j2==null? 0 : 1 
      })
   .GroupBy(o=>o.ParentId) 
   .Select(o=>new { ParentId = o.key, Count = o.Sum(p=>p.ChildId) })

fonte