动态创建GroupBy语句

时间:2012-05-25 14:49:29

标签: c# linq

我正在尝试动态重新构建一些要在树视图中显示的数据,这将允许用户选择最多三个以下维度来按以下方式对数据进行分组:

Organisation
Company
Site
Division
Department

因此,例如,如果用户选择他们想要按公司分组,那么网站然后分部......以下代码将执行所需的分组。

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

这会给出一个像这样的结构:

+ Company A
  + Site A
    + Division 1
    + Division 2
  + Site B
    + Division 1
+ Company B
  + Site C
    + Division 2
+ Company C
  + Site D

但是,这只能为我提供大量的组合。

我如何将其转换为可以根据用户选择的三个维度动态创建等效表达式的内容,因此我不必为每个组合创建每个表达式中的一个!!?

谢谢你们。

2 个答案:

答案 0 :(得分:4)

一个有趣的问题。选择一种类型的分组键和另一种类型的结果...使得很有可能得到你所要求的。

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

}

以这种方式使用:

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

答案 1 :(得分:0)

另一种选择是使用DynamicLinq。如果这是直接LINQ(不是通过某些DB上下文,如LINQ2SQL),那么可以通过编写分组/选择器字符串来完成:

var entities = orgEntities
    .GroupBy("new(CompanyID, CompanyName)", "it", null) // DynamicLinq uses 'it' to reference the instance variable in lambdas.
    .Select(grp1 => new TreeViewItem
    {
        ...
        .GroupBy("new(SiteID, o.SiteName)", "it", null)
        // And so on...

您可以将其抽象为每种条件类型。我看到的唯一问题是内部分组可能不是最容易编译在一起的,但至少这可以让你从某个方向开始。 DynamicLinq允许您构建动态类型,因此可以进一步抽象它。最终,最大的挑战是根据您所分组的内容,生成的TreeViewItem包含不同的信息。动态LINQ的良好用例,但我看到的唯一问题是进一步向下抽象(到内部分组)。

让我们知道你提出了什么,这绝对是我以前没有考虑过的一个有趣的想法。