Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Group Multiple Items using GroupBy()?

Tags:

c#

linq

group-by

Consider the following simplified objects and relationships:

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

The above models a relationship that a Job is associated with one Discipline and one WorkCategory. The Workcategory is always a child of a specific parent Discipline (one-to-many relationship). We can assume that the relationship of the assigned Discipline and Workcategory will always be valid.

The issue I am facing is when I am trying to create a Grouping result object based on filters that are applied to Jobs. I am not sure If this can be done or if the approach I am taking is even correct. The exact question itself is not clear to me, however the above defines the problem statement.

  1. Can the design be improved?
  2. How can I group Jobs by Discipline and Workcategory?
  3. Do I even need the Grouping class?

I have tried the following (this is my first attempt using Linq), but to no success as my understanding is not complete enough. The other alternative is to first get the Discipline group and loop through the original grouping picking up the related 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
                             }});

Edit: After having posted this, I realized that the inital GroupBy parameters result in one group of item whereas I am looking for Group-SubGroup result.

Edit 2: To clarify a bit further, I dont want the associated Jobs as part of the result, but rather the Discipline-Workcategory grouping - thus the reason for the Grouping class


Initial solution based on @Obalix

Edit 7-Mar-2010:

This solution does not work - The GroupBy on the object Discipline will yield a unique grouping for each object. I think this is due to it being a reference type. Am I correct? I initially accepted this as the answer, however after some head scratching realised that my mock data itself was faulty. The initial questions still remain answered.

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()
              });
like image 699
Ahmad Avatar asked Feb 28 '10 12:02

Ahmad


2 Answers

Here is a description how you can implement an hierarchical grouping mechanism.

A generic way of doing this using LINQ (as shown in the link) is:

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

However, the link also describes an alternative way which allows you to do the following:

var groupedList = list.GroupByMany(x => x.disc, x => x.workCat);

Edit: Following Ahmad's comment here is the strongly typed version:

var groupedList = list.GroupBy(x => x.disc)
    .Select(g => new Grouping {
        parent = g.Key,
        children = g.GroupBy(x => x.workCat).ToList()
    });
like image 63
AxelEckenberger Avatar answered Oct 03 '22 06:10

AxelEckenberger


I'd use Linq syntax:

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

I find that easier to read than lots of chained methods with lambda-function parameters.

You can get your groupings from this:

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 
    };
like image 20
Keith Avatar answered Oct 03 '22 04:10

Keith