【问题标题】:LINQ GroupBy confusionLINQ GroupBy 混淆
【发布时间】:2012-01-06 14:30:59
【问题描述】:

我有

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“table”包含

{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 输出我得到 2 个 SQL 查询。

问题

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

【问题讨论】:

    标签: c# linq group-by


    【解决方案1】:

    问题是,您只能按 ID 分组 - 如果您在 SQL 中这样做,您也无法访问其他字段...

    要同时拥有其他字段,您必须将它们包含在您的组子句中:

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

    【讨论】:

      【解决方案2】:

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

      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 }
      

      【讨论】:

      • 为什么不使用 lambda 语法。
      【解决方案3】:

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

      如果 Revision 对于具有相同 ID 的所有行都相同,您可以使用 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 的分组.

      【讨论】:

      • 是的,但是在上面执行 Select 很糟糕,我认为 Tobias 的解决方案实际上是一个解决方案。因为它产生单一的SQL查询,所以单一的执行和数据恢复很重要。
      猜你喜欢
      • 2023-03-29
      • 1970-01-01
      • 1970-01-01
      • 2016-11-28
      • 1970-01-01
      • 1970-01-01
      • 2017-07-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多