LINQ - Unir a la izquierda, agrupar por y contar

166

Digamos que tengo este SQL:

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

¿Cómo puedo traducir esto a LINQ to SQL? Me atasqué en COUNT (c.ChildId), el SQL generado siempre parece generar COUNT (*). Esto es lo que obtuve hasta ahora:

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

¡Gracias!

pbz
fuente

Respuestas:

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
fuente
OK, eso funciona, pero ¿por qué? ¿Cómo lo piensas? ¿Cómo no contar valores nulos nos da lo mismo que COUNT (c.ChildId)? Gracias.
pbz
44
Así es como funciona SQL. COUNT (fieldname) contará las filas en ese campo que no son nulas. Tal vez no entiendo tu pregunta, aclara si ese es el caso.
Mehrdad Afshari
Supongo que siempre pensé en ello en términos de contar filas, pero tienes razón, solo se cuentan los valores no nulos. Gracias.
pbz
1
.Count () generará COUNT (*) que contará todas las filas de ese grupo, por cierto.
Mehrdad Afshari
Tuve exactamente el mismo problema, sin embargo, comparar t => t.ChildID! = Null no funcionó para mí. El resultado siempre fue un objeto nulo y Resharper se quejó de que la expresión siempre era cierta. Así que usé (t => t! = Null) y eso funcionó para mí.
Joe
55

Considere usar una 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 } ;

Si los tipos de consulta están conectados por una asociación, esto se simplifica a:

from p in context.ParentTable 
let cCount = p.Children.Count()
select new { ParentId = p.Key, Count = cCount } ;
Amy B
fuente
Si no recuerdo mal (ha pasado un tiempo), esa consulta fue una versión simplificada de una grande. Si todo lo que necesitaba fuera la clave y contar, su solución hubiera sido más limpia / mejor.
pbz
1
Su comentario no tiene sentido en contexto con la pregunta original y las respuestas votadas. Además, si desea más que la clave, tiene toda la fila principal para dibujar.
Amy B
La solución con la letpalabra clave generará una subconsulta igual que la solución unida al grupo @Mosh.
Mohsen Afshin
@MohsenAfshin sí, genera una subconsulta igual a la consulta con una subconsulta en mi respuesta directamente encima de ella.
Amy B
39

RESPUESTA TARDÍA:

No debería necesitar la combinación izquierda en absoluto si todo lo que está haciendo es Count (). Tenga en cuenta que en join...intorealidad se traduce a lo GroupJoinque devuelve agrupaciones, new{parent,IEnumerable<child>}por lo que solo necesita llamar Count()al 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() }

En la sintaxis del Método de extensión a join intoes equivalente a GroupJoin(mientras que a joinsin an intoes 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
fuente
8

Si bien la idea detrás de la sintaxis LINQ es emular la sintaxis SQL, no siempre debe pensar en traducir directamente su código SQL a LINQ. En este caso particular, no necesitamos hacer group into ya que join into es un grupo en sí.

Aquí está mi solución:

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

A diferencia de la solución más votada aquí, no necesitamos j1 , j2 y comprobación nula en Count (t => t.ChildId! = Null)

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

fuente