【问题标题】:LINQ GroupBy with a dynamic group of columns具有动态列组的 LINQ GroupBy
【发布时间】:2015-10-22 16:45:59
【问题描述】:

我有一张这样的桌子:

 variety category  quantity
----------------------------------------------
  rg      pm         10
  gs      pm          5
  rg      com         8

我想根据这些bool参数制作一个GroupBy

  • IncludeVariety
  • IncludeCategory

例如:

IncludeVariety = true;
IncludeCategory = true;

会返回这个:

 variety category  quantity
----------------------------------------------
  rg      pm         10
  gs      pm          5
  rg      com         8

还有这个:

IncludeVariety = true;
IncludeCategory = false;

会返回这个:

  variety category  quantity
----------------------------------------------
    rg      -         18
    gs      -          5

还有这个:

IncludeVariety = false;
IncludeCategory = true;

会返回这个:

  variety category  quantity
----------------------------------------------
     -      pm         15
     -      com         8

你懂的……

问题:如何使用 LINQ 实现这一目标?

重要提示:我已将问题简化为 两个布尔变量IncludeVarietyIncludeCategory),但实际上我会有更多列 (说五个)

我不知道如何动态生成查询(.GroupBy.Select):

 rows.GroupBy(r => new { r.Variety, r.Category })
 .Select(g => new 
  {
        Variety = g.Key.Variety,
        Category = g.Key.Category,
        Quantity = g.Sum(a => a.Quantity),
  });

 rows.GroupBy(r => new { r.Category })
 .Select(g => new 
  {
        Variety = new {},
        Category = g.Key.Category,
        Quantity = g.Sum(a => a.Quantity),
  });

 rows.GroupBy(r => new { r.Variety })
 .Select(g => new 
  {
        Variety = g.Key.Variety,
        Category = new {},
        Quantity = g.Sum(a => a.Quantity),
  });

我过去做过的类似的事情是连接Where,比如:

  var query = ...

  if (foo) {
       query = query.Where(...)
  }

  if (bar) {
       query = query.Where(...)
  }

  var result = query.Select(...)

我可以在这里做类似的事情吗?

【问题讨论】:

    标签: c# linq group-by


    【解决方案1】:
    var results=items
      .Select(i=>
        new {
          variety=includevariety?t.variety:null,
          category=includecategory?t.category:null,
          ...
        })
      .GroupBy(g=>
        new { variety, category, ... }, g=>g.quantity)
      .Select(i=>new {
        variety=i.Key.variety,
        category=i.Key.category,
        ...
        quantity=i.Sum()
      });
    

    缩短:

    var results=items
      .GroupBy(g=>
        new {
          variety=includevariety?t.variety:null,
          category=includecategory?t.category:null,
          ... 
        }, g=>g.quantity)
      .Select(i=>new {
        variety=i.Key.variety,
        category=i.Key.category,
        ...
        quantity=i.Sum()
      });
    

    【讨论】:

    • 你是个天才!我会试试这个并接受答案以防万一
    • 如果您愿意,也可以通过组合第一个 select 和 groupby 来缩短它,但我认为这更具可读性,但这是非常主观的。
    • 哦,好的。我忘了空条件。我删除了我的答案xD
    • 伙计,它就像一个魅力!我也有一些 id,所以如果有人感兴趣:Category = includeCategory ? r.Category : null, CategoryId = includeCategory ? r.CategoryId : (long?)null -- 需要(long?)null
    【解决方案2】:

    如果您需要它是真正动态的,请使用 Scott Gu 的 Dynamic LINQ 库。

    您只需要弄清楚要在结果中包含哪些列并按它们分组。

    public static IQueryable GroupByColumns(this IQueryable source,
        bool includeVariety = false,
        bool includeCategory = false)
    {
        var columns = new List<string>();
        if (includeVariety) columns.Add("Variety");
        if (includeCategory) columns.Add("Category");
        return source.GroupBy($"new({String.Join(",", columns)})", "it");
    }
    

    然后你可以将它们分组。

    var query = rows.GroupByColumns(includeVariety: true, includeCategory: true);
    

    【讨论】:

    • 如果您需要动态结果,这是一个很好的解决方案。我通常会尽可能避免这种情况,但有时你就是做不到,这就是这个答案的完美之处。
    • 为什么比你的答案更好?
    【解决方案3】:

    在任何情况下,仍然需要在没有动态 LINQ 和类型安全的情况下按动态列进行分组。您可以向此 IQueryable 扩展方法提供一个匿名对象,其中包含您可能想要分组的所有属性(具有匹配的类型!)以及您想要用于此组调用的属性名称列表。在第一个重载中,我使用匿名对象来获取它的构造函数和属性。我使用它们通过表达式动态地构建组。在第二个重载中,我将匿名对象仅用于 TKey 的类型推断,不幸的是,在 C# 中没有办法解决,因为它具有有限的类型别名能力。
    仅适用于像这样的可为空的属性,可能很容易为非空值扩展,但现在不能打扰

    public static IQueryable<IGrouping<TKey, TElement>> GroupByProps<TElement, TKey>(this IQueryable<TElement> self, TKey model, params string[] propNames)
    {
        var modelType = model.GetType();
        var props = modelType.GetProperties();
        var modelCtor = modelType.GetConstructor(props.Select(t => t.PropertyType).ToArray());
    
        return self.GroupByProps(model, modelCtor, props, propNames);
    }
    
    public static IQueryable<IGrouping<TKey, TElement>> GroupByProps<TElement, TKey>(this IQueryable<TElement> self, TKey model, ConstructorInfo modelCtor, PropertyInfo[] props, params string[] propNames)
    {
        var parameter = Expression.Parameter(typeof(TElement), "r");
        var propExpressions = props
            .Select(p =>
            {
                Expression value;
    
                if (propNames.Contains(p.Name))
                    value = Expression.PropertyOrField(parameter, p.Name);
                else
                    value = Expression.Convert(Expression.Constant(null, typeof(object)), p.PropertyType);
    
                return value;
            })
            .ToArray();
    
        var n = Expression.New(
            modelCtor,
            propExpressions,
            props
        );
    
        var expr = Expression.Lambda<Func<TElement, TKey>>(n, parameter);
        return self.GroupBy(expr);
    }
    

    我实现了两个重载,以防您想缓存构造函数和属性以避免每次调用时的反射。像这样使用:

    //Class with properties that you want to group by
    class Record
    {
        public string Test { get; set; }
        public int? Hallo { get; set; }
        public DateTime? Prop { get; set; }
    
        public string PropertyWhichYouNeverWantToGroupBy { get; set; }
    }
    
    //usage
    
    IQueryable<Record> queryable = ...; //the queryable
    
    var grouped = queryable.GroupByProps(new
    {
        Test = (string)null,        //put all properties that you might want to group by here
        Hallo = (int?)null,
        Prop = (DateTime?)null
    }, nameof(Record.Test), nameof(Record.Prop));   //This will group by Test and Prop but not by Hallo
    
    //Or to cache constructor and props
    var anonymous = new
    {
        Test = (string)null,        //put all properties that you might want to group by here
        Hallo = (int?)null,
        Prop = (DateTime?)null
    };
    var type = anonymous.GetType();
    var constructor = type.GetConstructor(new[]
    {
        typeof(string),             //Put all property types of your anonymous object here 
        typeof(int?),
        typeof(DateTime?)
    });
    var props = type.GetProperties();
    //You need to keep constructor and props and maybe anonymous 
    //Then call without reflection overhead
    queryable.GroupByProps(anonymous, constructor, props, nameof(Record.Test), nameof(Record.Prop));
    
    

    您将作为 TKey 接收的匿名对象将仅填充您用于分组的键(即在此示例中为“测试”和“道具”),其他的将为空。

    【讨论】:

      猜你喜欢
      • 2011-04-25
      • 1970-01-01
      • 2020-01-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多