LINQ - łączenie lewostronne, grupowanie według i liczenie

166

Powiedzmy, że mam ten SQL:

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

Jak mogę przetłumaczyć to na LINQ to SQL? Utknąłem na COUNT (c.ChildId), wygenerowany kod SQL zawsze wydaje się wyprowadzać COUNT (*). Oto, co mam do tej pory:

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

Dziękuję Ci!

pbz
źródło

Odpowiedzi:

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
źródło
OK, to działa, ale dlaczego? Jak o tym myślisz? W jaki sposób nie liczenie wartości null daje nam to samo, co COUNT (c.ChildId)? Dzięki.
pbz
4
Tak działa SQL. COUNT (nazwa pola) zlicza wiersze w tym polu, które nie są puste. Może nie dostałem twojego pytania, proszę wyjaśnij, czy tak jest.
Mehrdad Afshari
Chyba zawsze myślałem o tym w kategoriach liczenia wierszy, ale masz rację, liczone są tylko wartości inne niż null. Dzięki.
pbz
1
.Count () wygeneruje COUNT (*), który przy okazji policzy wszystkie wiersze w tej grupie.
Mehrdad Afshari
Miałem dokładnie ten sam problem, jednak porównanie t => t.ChildID! = Null nie działało dla mnie. Rezultatem był zawsze obiekt zerowy, a Resharper narzekał, że wyrażenie było zawsze prawdziwe. Więc użyłem (t => t! = Null) i to zadziałało dla mnie.
Joe
55

Rozważ użycie podzapytania:

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

Jeśli typy zapytań są połączone asocjacją, upraszcza to:

from p in context.ParentTable 
let cCount = p.Children.Count()
select new { ParentId = p.Key, Count = cCount } ;
Amy B.
źródło
Jeśli dobrze pamiętam (minęło trochę czasu), to zapytanie było uproszczoną wersją dużego. Gdybym potrzebował tylko klucza i liczę, Twoje rozwiązanie byłoby czystsze / lepsze.
pbz
1
Twój komentarz nie ma sensu w kontekście oryginalnego pytania i pozytywnych odpowiedzi. Dodatkowo - jeśli chcesz więcej niż klucz, masz cały wiersz nadrzędny do rysowania.
Amy B,
Rozwiązanie ze letsłowem kluczowym wygeneruje podzapytanie tak samo jak rozwiązanie przyłączone do grupy @Mosh.
Mohsen Afshin
@MohsenAfshin tak, generuje podzapytanie takie samo jak zapytanie z podzapytaniem w mojej odpowiedzi bezpośrednio nad nim.
Amy B
39

SPÓŹNIONA ODPOWIEDŹ:

W ogóle nie powinieneś potrzebować lewego złączenia, jeśli wszystko, co robisz, to Count (). Zauważ, że w join...intorzeczywistości jest przetłumaczone na GroupJoinktóre zwraca grupowanie, tak new{parent,IEnumerable<child>}więc wystarczy zadzwonić Count()do grupy:

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

W składni metody rozszerzenia a join intojest równoważne GroupJoin(podczas gdy a joinbez intojest 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
źródło
8

Chociaż ideą składni LINQ jest emulacja składni SQL, nie należy zawsze myśleć o bezpośrednim tłumaczeniu kodu SQL na LINQ. W tym konkretnym przypadku nie musimy włączać grupy w, ponieważ dołączanie do jest dołączaniem do samej grupy.

Oto moje rozwiązanie:

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

W przeciwieństwie do najczęściej wybieranego tutaj rozwiązania, nie potrzebujemy sprawdzania j1 , j2 i null w Count (t => t.ChildId! = Null)

Mosh
źródło
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) })

źródło