LINQ - Rejoindre gauche, regrouper par et compter

166

Disons que j'ai ce SQL:

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

Comment puis-je traduire cela en LINQ to SQL? Je suis resté bloqué au COUNT (c.ChildId), le SQL généré semble toujours afficher COUNT (*). Voici ce que j'ai jusqu'à présent:

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

Je vous remercie!

pbz
la source

Réponses:

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
la source
OK, ça marche, mais pourquoi? Comment y réfléchissez-vous? Comment ne pas compter les valeurs nulles nous donne-t-il la même chose que COUNT (c.ChildId)? Merci.
pbz
4
C'est ainsi que fonctionne SQL. COUNT (fieldname) comptera les lignes de ce champ qui ne sont pas nulles. Peut-être que je ne comprends pas votre question, veuillez préciser si c'est le cas.
Mehrdad Afshari
Je suppose que j'ai toujours pensé à cela en termes de comptage des lignes, mais vous avez raison, seules les valeurs non nulles sont comptées. Merci.
pbz
1
.Count () générera COUNT (*) qui comptera toutes les lignes de ce groupe, d'ailleurs.
Mehrdad Afshari
J'ai eu exactement le même problème, mais comparer t => t.ChildID! = Null ne fonctionnait pas pour moi. Le résultat était toujours un objet nul et Resharper s'est plaint que l'expression était toujours vraie. J'ai donc utilisé (t => t! = Null) et cela a fonctionné pour moi.
Joe
55

Pensez à utiliser une sous-requête:

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 les types de requêtes sont connectés par une association, cela se simplifie en:

from p in context.ParentTable 
let cCount = p.Children.Count()
select new { ParentId = p.Key, Count = cCount } ;
Amy B
la source
Si je me souviens bien (cela fait un moment), cette requête était une version simplifiée d'une grande. Si tout ce dont j'avais besoin était la clé et le décompte, votre solution aurait été plus propre / meilleure.
pbz le
1
Votre commentaire n'a pas de sens dans le contexte de la question originale et des réponses votées. De plus, si vous voulez plus que la clé, vous avez toute la ligne parent à partir de laquelle vous pouvez dessiner.
Amy B
La solution avec le letmot clé générera une sous-requête identique à la solution jointe au groupe @Mosh.
Mohsen Afshin
@MohsenAfshin oui, il génère une sous-requête identique à la requête avec une sous-requête dans ma réponse directement au-dessus.
Amy B
39

RÉPONSE TARDIVE:

Vous ne devriez pas du tout avoir besoin de la jointure gauche si vous ne faites que Count (). Notez que join...intoc'est en fait traduit dans GroupJoinquels groupes retourne comme new{parent,IEnumerable<child>}si vous avez juste besoin d'appeler Count()le groupe:

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

Dans la syntaxe de la méthode d'extension, a join intoest équivalent à GroupJoin(tandis que a joinsans an intoest 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
la source
8

Alors que l'idée derrière la syntaxe LINQ est d'émuler la syntaxe SQL, vous ne devriez pas toujours penser à traduire directement votre code SQL en LINQ. Dans ce cas particulier, nous n'avons pas besoin de faire de groupe dans car rejoindre est un groupe se joindre.

Voici ma solution:

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

Contrairement à la solution la plus votée ici, nous n'avons pas besoin de vérifier j1 , j2 et null dans Count (t => t.ChildId! = Null)

Mosh
la source
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) })

la source