LINQ - левое соединение, группировка и подсчет

166

Допустим, у меня есть этот SQL:

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

Как я могу перевести это в LINQ to SQL? Я застрял в COUNT (c.ChildId), сгенерированный SQL всегда, кажется, выводит COUNT (*). Вот что я получил так далеко:

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

Спасибо!

PBZ
источник

Ответы:

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) }
Мехрдад Афшари
источник
Хорошо, это работает, но почему? Как вы думаете через это? Как подсчет нулевых значений дает нам то же самое, что и COUNT (c.ChildId)? Спасибо.
pbz
4
Вот как работает SQL. COUNT (имя поля) будет считать строки в этом поле, которые не являются нулевыми. Может быть, я не понимаю ваш вопрос, пожалуйста, уточните, если это так.
Мехрдад Афшари
Я думаю, я всегда думал об этом с точки зрения подсчета строк, но вы правы, подсчитываются только ненулевые значения. Спасибо.
пбз
1
.Count () сгенерирует COUNT (*), который, кстати, будет подсчитывать все строки в этой группе.
Мехрдад Афшари
У меня была точно такая же проблема, однако сравнение t => t.ChildID! = Null у меня не сработало. Результатом всегда был нулевой объект, и Решарпер пожаловался, что выражение всегда было истинным. Поэтому я использовал (t => t! = Null), и это сработало для меня.
Джо
55

Рассмотрите возможность использования подзапроса:

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

Если типы запросов связаны ассоциацией, это упрощается до:

from p in context.ParentTable 
let cCount = p.Children.Count()
select new { ParentId = p.Key, Count = cCount } ;
Эми Б
источник
Если я правильно помню (это было давно), этот запрос был упрощенной версией большого. Если бы все, что мне было нужно, было ключом и считать, что ваше решение было бы чище / лучше.
pbz
1
Ваш комментарий не имеет смысла в контексте с оригинальным вопросом и ответами с голосованием. Дополнительно - если вы хотите больше, чем ключ, у вас есть вся родительская строка для рисования.
Эми Б
Решение с letключевым словом сгенерирует подзапрос так же, как и решение, объединенное с группой @Mosh.
Мохсен Афшин
@MohsenAfshin да, он генерирует подзапрос так же, как запрос с подзапросом в моем ответе прямо над ним.
Эми Б
39

ПОЗДНО ОТВЕТ:

Вам вообще не нужно левое соединение, если все, что вы делаете, это Count (). Обратите внимание, что join...intoна самом деле переводится на GroupJoinвозвращаемые группировки, new{parent,IEnumerable<child>}так что вам просто нужно вызвать Count()группу:

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

В Синтаксисе метода расширения a join intoэквивалентно GroupJoin(а a joinбез intois Join):

context.ParentTable
    .GroupJoin(
                   inner: context.ChildTable
        outerKeySelector: parent => parent.ParentId,
        innerKeySelector: child => child.ParentId,
          resultSelector: (parent, children) => new { parent.Id, Count = children.Count() }
    );
Эрен Эрсонмез
источник
8

Хотя идея синтаксиса LINQ заключается в том, чтобы эмулировать синтаксис SQL, вы не всегда должны думать о прямом переводе своего кода SQL в LINQ. В данном конкретном случае нам не нужно делать group into, так как join into - это само присоединение группы.

Вот мое решение:

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

В отличие от решения с большинством голосов, мы здесь не нуждаемся в проверке j1 , j2 и нуля в Count (t => t.ChildId! = Null)

Мош
источник
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) })

источник