LINQ - 左连接、分组和计数

176

假设我有以下 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() }

谢谢!

5个回答

199
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(fieldname)会计算该字段中非空行的数量。也许我没理解你的问题,请说明一下如果是这种情况。 - Mehrdad Afshari
我想我一直以为它是按行计数,但你是正确的,只有非空值才被计算。谢谢。 - pbz
1
.Count() 会生成 COUNT(*),它将计算该组中的所有行,顺便说一句。 - Mehrdad Afshari
我曾经遇到过完全相同的问题,但是将t=>t.ChildID != null进行比较并没有起作用。结果总是一个空对象,并且Resharper抱怨表达式总是为真。所以我使用了(t => t != null),这对我有用。 - Joe
这里不需要使用"group by",因为LINQ中的"join into"本质上就是分组连接。此外,"j1"、"j2"和空值检查也不是必需的,它们只会增加噪音。请参见下面的答案,以获取更简洁的解决方案。 - Mosh

63

考虑使用子查询:

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
你的评论与原问题和已点赞的答案不符合上下文。此外,如果你需要更多的信息,你可以从整个父行中获取。 - Amy B
使用 let 关键字的解决方案将生成一个子查询,与 @Mosh 组合并的解决方案相同。 - Mohsen Afshin
@MohsenAfshin 是的,它生成一个子查询,与我在直接上面答案中有一个子查询的查询相同。 - Amy B

39

晚回答:

如果你只是在进行计数,那么根本不需要左连接。请注意,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() }

在扩展方法语法中,join into 相当于 GroupJoin(而没有 intojoin 则相当于 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() }

与大多数投票解决方案不同,我们在 Count(t => t.ChildId != null) 中不需要 j1j2 和空值检查。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接