如何使用GroupBy()对多个项目进行分组?

6

考虑以下简化的对象和关系:

public class Job{
  int JobId;
  String name;
  String status;
  Discipline disc;
  WorkCategory workCat;
}

public class Discipline {
  int DisciplineId;
  String DisciplineName;
}

public class Workcategory{
   int WorkCategoryId;
   String WorkCategoryName;
}

public Class Grouping{
   Discipline parent;
   List<Workcategory> children;
}

上面的模型展示了一个关系,即一个 Job 与一个 Discipline 和一个 WorkCategory 相关联。 Workcategory 总是作为特定父 Discipline 的子代(一对多关系)。 我们可以假设分配的学科和工作类别的关系始终有效。
我遇到的问题是,当我尝试根据应用于 Jobs 的过滤器创建 Grouping 结果对象时。我不确定这是否可以完成,或者我所采取的方法是否正确。确切的问题本身对我来说不太清楚,但是上述定义了问题陈述。
  1. 设计能否改进?
  2. 如何按学科和工作类别对工作进行分组?
  3. 我是否需要 Grouping 类?
我尝试过以下操作(这是我第一次使用Linq),但由于我的理解还不够完善,所以没有成功。另一种选择是先获取 Discipline 组,然后循环原始分组并提取相关的 Workcategory
var grouping = repository.GetJobsWithActiveStatus()
            .GroupBy(x => new {
                                  x.Discipline.DisciplineID, 
                                  x.Discipline.DisciplineName,  
                                  x.Category.WorkCategoryID, 
                                  x.Category.WorkCategoryName
                              })
            .Select(g => new Grouping{
                                 Discipline = new Discipline{
                                                  DisciplineID = g.Key.DisciplineID, 
                                                  Name = g.Key.DisciplineName
                                                  }, 
                                 Categories = ?? // this is where I am lost
                             }});

编辑: 发帖后,我意识到最初的GroupBy参数会导致一组项目,而我正在寻找Group-SubGroup结果。

编辑2: 为了进一步澄清,我不想让相关的工作成为结果的一部分,而是要按照学科-工作类别进行分组 - 这就是Grouping类的原因。


@Obalix基于初始解决方案

编辑7-Mar-2010:

这个解决方案不起作用 - 在对象Disipline上的GroupBy将为每个对象产生一个唯一的分组。我认为这是由于它是引用类型造成的。我最初接受了这个答案,但在经过一些深思熟虑后意识到我的模拟数据本身有问题。最初的问题仍然得到了回答。

var result = repository.GetJobsWithActiveStatus()
      .GroupBy(x => x.disc)
      .Select(g => new Grouping
              {
                  Discipline = g.Key,
                  Catergories = g.GroupBy(x=>x.workCat) // <--- The Problem is here, grouping on a reference type
                                 .Select(l=>l.Key) // needed to select the Key's only
                                 .ToList()
              });
4个回答

2

这里有一个说明如何实现分层分组机制。

使用LINQ的通用方法(如链接中所示)是:

var list = new List<Job>();

var groupedList = list.GroupBy(x => x.disc)
    .Select(g => new {
        Key = g.Key,
        Count = g.Count(),
        WorkCategoryGroups = g.GroupBy(x => x.workCat)
    });

然而,此链接还提供了一种替代方法,使您能够实现以下内容:
var groupedList = list.GroupByMany(x => x.disc, x => x.workCat);

编辑:根据Ahmad的评论,这里是强类型版本:

var groupedList = list.GroupBy(x => x.disc)
    .Select(g => new Grouping {
        parent = g.Key,
        children = g.GroupBy(x => x.workCat).ToList()
    });

我已经查看了链接并阅读了评论,这让我找到了另一篇文章,所以我会进一步查看。然而,我正在寻找一个强类型的结果(分组)。 - Ahmad
然后,您只需将“new { }”替换为“new StrongType { }”。使用您自定义的成员代替“Key”,“Count”和“WorkCategoryGroups”。链接描述的扩展方法是强类型的。 - AxelEckenberger
FYI - 上面回答中发布的内容很棒 - 如果有人感兴趣,还有一个关于动态GroupByMany的后续帖子 http://blogs.msdn.com/mitsu/archive/2008/02/07/linq-groupbymany-dynamically.aspx - Ahmad
我已将此标记取消为答案 - 据我所知,对对象进行的选择和分组针对每个组合产生唯一结果。例如,即使我可以拥有两个具有相同学科和唯一工作类别的工作 - 结果将给出两个组。我认为这是因为在引用类型对象上进行比较所导致的。 - Ahmad
@Ahmad:回答你的问题时,我只能根据你提供的信息进行。你的解决方案与我的几乎相同——除了扩展分组——所以它带领你找到了正确的答案。除非有其他答案(不是你自己提供的)从你提供的信息中得出了正确的结论,否则剥夺奖励是不好的。否则,每个发帖人都会添加最后一篇帖子并接受自己的解决方案,从而否认那些花时间引导他们找到最终解决方案的人的奖励。 - AxelEckenberger
如果看起来是那样的话,我必须道歉。你对我的回答与其他人的回答有很大的不同。所有发布的回复,包括你的回复,都导致了相同的结果,而这并不是我要寻找的答案。然而,看一下我最初发布的解决方案,它更接近于我的解决方案。问题如注所述,涉及到引用类型和对象的分组(这是几乎所有答案中的一个相似主题)。如果你想让你的解决方案重新标记为正确答案,没问题...编辑-我已将其标记为正确答案。 - Ahmad

2

我会使用Linq语法:

var jobsByDiscipline = 
    from j in repository.GetJobsWithActiveStatus()
    group j by j.Discipline.DisciplineID into g
    select new { 
        DisciplineID = g.Key,
        Jobs = g.ToList() 
    };

var jobsByCategory = 
    from j in repository.GetJobsWithActiveStatus()
    group j by j.Workcategory.WorkcategoryID into g
    select new { 
        WorkcategoryID = g.Key,
        Jobs = g.ToList() 
    };

我发现这比使用带有lambda函数参数的大量串联方法更易于阅读。
您可以从这里获取您的分组:
var disciplineAndCategory = 
    from j in repository.GetJobsWithActiveStatus()
    group j by j.Discipline.DisciplineID into g
    let categories = 
        from j2 in g
        select j2.Workcategory.WorkcategoryID
    select new { 
        DisciplineID = g.Key,
        Jobs = categories.Distinct() // each category id once 
    };

1
+1 同意,使用这种语法在进行分组时阅读起来更加容易。 - Nick Craver
@keith,感谢您的建议,我第一次尝试使用它似乎很有效 - 但是没有经过测试,不过我更喜欢链式方法的方式 - 只是个人偏好 :) - Ahmad

0

这里有另一种不需要 Grouping 类的方法。

ILookup<Discipline, Workcategory> result = repository
  .GetJobsWithActiveStatus()
  .Select(job => new {disc = job.disc, workCat = job.workCat})
  .Distinct()
  .ToLookup(x => x.disc, x => x.workCat);

经过测试,它产生了与Obalix答案类似的结果。每个学科和工作类别都有一个分组。 - Ahmad

0

这是适用于我的解决方案。

我首先需要获取所有的学科组,然后创建相关的工作类别列表,其中学科ID相等。

var result = liveJobs
.GroupBy(x => new {
               x.disc.DisciplineID, 
               x.disc.DisciplineName
                  })
.Select(g => new Grouping()
              {
               parent = new Discipline(g.Key.DisciplineID,g.Key.DisciplineName),
               children = g.Where(job=>job.disc.DisciplineID == g.Key.DisciplineID) // filter the current job discipline to where the group key disciplines are equal
              .Select(j=>j.workCat)
              .ToList()
              });

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