LINQ群情激愤

本文关键字:LINQ | 更新日期: 2023-09-27 18:19:42

我有

var result =  (from rev in Revisions 
   join usr in Users on rev.UserID equals usr.ID
    join clc in ChangedLinesCounts on rev.Revision equals clc.Revision
    select new {rev.Revision, 
    rev.Date, usr.UserName, usr.ID, clc.LinesCount}).Take(6);

我在不同的表上进行了几个连接,与这个问题无关,键是什么,但在这个查询的末尾,我的result"表"包含

{Revision, Date, UserName, ID, LinesCount}

现在我执行e GroupBy来计算每个用户的总行数。

所以。。

from row in result group row by row.ID into g  {1}
    select new { 
        g.Key,
        totalCount = g.Sum(count=>count.LinesCount)
    };

所以我得到了一个Key=IDtotalCount=Sum,但是

困惑

我也希望其他领域的最终结果。在我的理解中,{1}分组查询后的"表"由组成

{Revision, Date, UserName, ID, LinesCount, TotalCount}

如果我的假设是正确的,为什么我不能做这样的事情:

from row in result group row by row.ID into g  {1}
        select new { 
            g.Key,
                g.Revision //Revision doesn't exist ! Why ??
            totalCount = g.Sum(count=>count.LinesCount)
        };

from row in result group row by row.ID into g  {1}
            select new { 
                g.Key,
                    Revision = g.Select(x=>x.Revision), //Works !
                totalCount = g.Sum(count=>count.LinesCount)
            };

工作!,但是imo太糟糕了,因为我执行了另一个Select

通过查看LinqPad SQL输出,我得到2SQL查询。

问题

有什么优雅和最佳的方法可以做到这一点吗?或者我总是需要运行Select在分组数据上,为了能够访问字段,是否存在?

LINQ群情激愤

问题是,您只能根据ID进行分组-如果您在SQL中这样做,您也无法访问其他字段。。。

要获得其他字段,您必须将它们包含在您的group子句中:

from row in result group row by new { row.ID, row.Revision } into g
    select new { 
        g.Key.ID,
        g.Key.Revision
        totalCount = g.Sum(count=>count.LinesCount)
    };

这里的问题是您的输出在逻辑上看起来像这样:

Key = 1
    Id = 1, Revision = 3587, UserName = Bob, LinesCount = 34, TotalCount = 45
    Id = 1, Revision = 3588, UserName = Joe, LinesCount = 64, TotalCount = 54
    Id = 1, Revision = 3589, UserName = Jim, LinesCount = 37, TotalCount = 26
Key = 2
    Id = 2, Revision = 3587, UserName = Bob, LinesCount = 34, TotalCount = 45
    Id = 2, Revision = 3588, UserName = Joe, LinesCount = 64, TotalCount = 54
    Id = 2, Revision = 3589, UserName = Jim, LinesCount = 37, TotalCount = 26

就像执行SQL GROUP BY一样,一个值要么是键的一部分,因此每个组都是唯一的,要么是在细节中,因此会重复多次,并且每行可能不同。

现在,从逻辑上讲,Revision和UserName可能对每个Id都是唯一的,但Linq无法知道这一点(就像SQL无法知道一样)。

要解决这个问题,你需要了解如何指定你想要的修订版。例如:

Revision = g.FirstOrDefault(x => x.Revision)

为了避免多SQL问题,您需要使用一个可以转换为SQL的聚合函数,因为大多数SQL方言都没有first运算符(结果集被认为是无序的,因此从技术上讲,没有项目是"第一")。

Revision = g.Min(x => x.Revision)
Revision = g.Max(x => x.Revision)

不幸的是,Linq没有字符串的min/max运算符,所以尽管SQL可能支持这一点,但Linq不支持。

在这种情况下,您可以为Id和总数生成一个中间结果集,然后将其连接回原始集以获取详细信息,例如:

from d in items
join t in (
    from t in items
    group by t.Id into g
    select new { Id = g.Key, Total = g.Sum(x => x.LineCount) }
) on d.Id equals t.Id
select new { Id = d.Id, Revision = d.Revision, Total = t.Total }

Revision在您的第二个示例中不存在,因为它不是IGrouping<T>的成员,在IGrouping<T>中,您有一个Key属性,它也是分组在一起的所有行的IEnumerable<T>。因此,这些行中的每一行都具有Revision,但对于分组本身没有Revision

如果具有相同ID的所有行的Revision都是相同的,则可以使用FirstOrDefault(),以便选择网络最多有一个答案:

from row in result group row by row.ID into g  {1}
            select new { 
                g.Key,
                Revision = g.Select(x=>x.Revision).FirstOrDefault(),
                totalCount = g.Sum(count=>count.LinesCount)
            };

但是,如果Revision不是每个ID唯一的,则您希望使用@Tobias建议的匿名类型进行分组,然后您将获得基于IDRevision的分组。