LINQ - Left Join, Group By und Count

166

Angenommen, ich habe diese SQL:

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

Wie kann ich dies in LINQ to SQL übersetzen? Ich blieb bei COUNT (c.ChildId) hängen, die generierte SQL scheint immer COUNT (*) auszugeben. Folgendes habe ich bisher erreicht:

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

Danke dir!

pbz
quelle

Antworten:

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
quelle
OK, das funktioniert, aber warum? Wie denkst du darüber nach? Wie ergibt das Nichtzählen von Nullwerten dasselbe wie COUNT (c.ChildId)? Vielen Dank.
pbz
4
So funktioniert SQL. COUNT (Feldname) zählt die Zeilen in diesem Feld, die nicht null sind. Vielleicht verstehe ich Ihre Frage nicht, bitte klären Sie, ob dies der Fall ist.
Mehrdad Afshari
Ich denke, ich habe immer darüber nachgedacht, Zeilen zu zählen, aber Sie haben Recht, nur die Nicht-Null-Werte werden gezählt. Vielen Dank.
pbz
1
.Count () generiert COUNT (*), das übrigens alle Zeilen in dieser Gruppe zählt.
Mehrdad Afshari
Ich hatte genau das gleiche Problem, aber der Vergleich von t => t.ChildID! = Null hat bei mir nicht funktioniert. Das Ergebnis war immer ein Nullobjekt und Resharper beschwerte sich, dass der Ausdruck immer wahr sei. Also habe ich (t => t! = Null) verwendet und das hat bei mir funktioniert.
Joe
55

Verwenden Sie eine Unterabfrage:

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

Wenn die Abfragetypen durch eine Zuordnung verbunden sind, vereinfacht sich dies zu:

from p in context.ParentTable 
let cCount = p.Children.Count()
select new { ParentId = p.Key, Count = cCount } ;
Amy B.
quelle
Wenn ich mich richtig erinnere (es ist eine Weile her), war diese Abfrage eine vereinfachte Version einer großen. Wenn ich nur den Schlüssel und die Zählung benötigt hätte, wäre Ihre Lösung sauberer / besser gewesen.
pbz
1
Ihr Kommentar ist im Zusammenhang mit der ursprünglichen Frage und den positiv bewerteten Antworten nicht sinnvoll. Zusätzlich - wenn Sie mehr als den Schlüssel möchten, können Sie die gesamte übergeordnete Zeile zeichnen.
Amy B
Die Lösung mit dem letSchlüsselwort generiert eine Unterabfrage, die mit der @ Mosh-Gruppe verbunden ist.
Mohsen Afshin
@MohsenAfshin Ja, es wird eine Unterabfrage generiert, die der Abfrage mit einer Unterabfrage in meiner Antwort direkt darüber entspricht.
Amy B
39

SPÄTE ANTWORT:

Sie sollten den linken Join überhaupt nicht benötigen, wenn Sie nur Count () ausführen. Beachten Sie, dass join...intotatsächlich übersetzt wird, in GroupJoinwelche Gruppierungen zurückgegeben werden, new{parent,IEnumerable<child>}sodass Sie nur Count()die Gruppe aufrufen müssen :

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

In der Syntax der Erweiterungsmethode join intoentspricht a GroupJoin(während a joinohne ein intoist 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
quelle
8

Während die Idee hinter der LINQ-Syntax darin besteht, die SQL-Syntax zu emulieren, sollten Sie nicht immer daran denken, Ihren SQL-Code direkt in LINQ zu übersetzen. In diesem speziellen Fall müssen wir keine Gruppierung durchführen, da die Verknüpfung eine Gruppenverbindung ist.

Hier ist meine Lösung:

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

Im Gegensatz zu dem meisten Lösung hier gestimmt hat , haben wir nicht brauchen j1 , j2 und null Prüfung in Count (t => t.ChildId! = Null)

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

quelle