Create GroupBy Statements Dynamically

c#, linq

Solution

An intriguing problem. Choosing a single type for grouping keys and another type for results... makes it is very possible to get what you're asking for.

public struct EntityGroupKey
{
  public int ID {get;set;}
  public string Name {get;set;}
}

public class EntityGrouper
{
  public Func<Entity, EntityGroupKey> KeySelector {get;set;}
  public Func<EntityGroupKey, TreeViewItem> ResultSelector {get;set;}
  public EntityGrouper NextGrouping {get;set;} //null indicates leaf level

  public List<TreeViewItem> GetItems(IEnumerable<Entity> source)
  {
    var query =
      from x in source
      group x by KeySelector(x) into g
      let subItems = NextGrouping == null ?
        new List<TreeViewItem>() :
        NextGrouping.GetItems(g)
      select new { Item = ResultSelector(g.Key), SubItems = subItems };

    List<TreeViewItem> result = new List<TreeViewItem>();
    foreach(var queryResult in query)
    {
          // wire up the subitems
      queryResult.Item.SubItems = queryResult.SubItems 
      result.Add(queryResult.Item);
    }
    return result;
  }

}

Used in this way:

EntityGrouper companyGrouper = new EntityGrouper()
{
  KeySelector = o => new EntityGroupKey() {ID = o.CompanyID, Name = o.CompanyName},
  ResultSelector = key => new TreeViewItem
  {
    CompanyID = key.ID,
    DisplayName = key.Name,
    ItemTypeEnum = TreeViewItemType.Company
  }
}

EntityGrouper divisionGrouper = new EntityGrouper()
{
  KeySelector = o => new EntityGroupKey() {ID = 0, Name = o.Division},
  ResultSelector = key => new TreeViewItem
  {
    DisplayName = key.Name,
    ItemTypeEnum = TreeViewItemType.Division
  } 
}

companyGrouper.NextGrouping = divisionGrouper;

List<TreeViewItem> oneWay = companyGrouper.GetItems(source);

companyGrouper.NextGrouping = null;
divisionGrouper.NextGrouping = companyGrouper;

List<TreeViewItem> otherWay = divisionGrouper.GetItems(source);

Problem

I am trying to dynamically re-structure some data to be shown in a treeview which will allows the user to select up to three of the following dimensions to group the data by: ``` Organisation Company Site Division Department ``` So for example, if the user were to select that they wanted to group by Company then Site then Division...the following code would perform the required groupings. ``` var entities = orgEntities // Grouping Level 1 .GroupBy(o => new { o.CompanyID, o.CompanyName }) .Select(grp1 => new TreeViewItem { CompanyID = grp1.Key.CompanyID, DisplayName = grp1.Key.CompanyName, ItemTypeEnum = TreeViewItemType.Company, SubItems = grp1 // Grouping Level 2 .GroupBy(o => new { o.SiteID, o.SiteName }) .Select(grp2 => new TreeViewItem { SiteID = grp2.Key.SiteID, DisplayName = grp2.Key.SiteName, ItemTypeEnum = TreeViewItemType.Site, SubItems = grp2 // Grouping Level 3 .GroupBy(o => new { o.Division }) .Select(grp3 => new TreeViewItem { DisplayName = grp3.Key.Division, ItemTypeEnum = TreeViewItemType.Division, }).ToList() }).ToList() }) .ToList(); ``` This would give a structre like this: ``` + Company A + Site A + Division 1 + Division 2 + Site B + Division 1 + Company B + Site C + Division 2 + Company C + Site D ``` However, this only provides me with on of a large number of combinations. How would I go about converting this into something that could create the equivalent expression dynamically based on the three dimensions that the user has chosen and so I don't have to create one of each of these expressions for each combination!!? Thanks guys.

Original source