【问题标题】:LINQ Grouping dynamicallyLINQ 动态分组
【发布时间】:2013-07-16 13:42:45
【问题描述】:

我有一个记录类列表,因此用户可以选择按属性名称动态分组行。例如MenuTextRoleNameActionName。然后我必须执行分组,所以我需要一个通用方法来通过传递列名来处理分组。

例子:

public class Menu
{
  public string MenuText {get;set;}
  public string RoleName {get;set;}
  public string ActionName {get;set;}
}

public class Menus 
{
 var list = new List<Menu>();
 list.Add( new Menu {MenuText="abc",RoleName ="Admin", ActionName="xyz"};
 list.Add( new Menu {MenuText="abc",RoleName ="Admin", ActionName="xyz"};
 list.Add( new Menu {MenuText="abc1",RoleName ="Admin1", ActionName="xyz1"};
 list.Add( new Menu {MenuText="abc1",RoleName ="Admin1", ActionName="xyz1"};

  /// columnName :- Name of the Menu class ( MenuText  or RoleName  or ActionName)

  public IEnumerable<IGrouping<string,IEnumerable<Menu>>> GroupMenu(string columnName)
  {
          // Here I want to get group the list of menu by the columnName 
  }
}

【问题讨论】:

    标签: c# .net linq group-by


    【解决方案1】:

    如果您不使用数据库,则可以使用反射:

    private static object GetPropertyValue(object obj, string propertyName)
    {
        return obj.GetType().GetProperty(propertyName).GetValue(obj, null);
    }
    

    用作:

    var grouped = enumeration.GroupBy(x => GetPropertyValue(x, columnName));
    

    这是一个非常原始的解决方案,更好的方法应该是使用Dynamic LINQ

    var grouped = enumeration.GroupBy(columnName, selector);
    

    EDIT Dynamic LINQ 可能需要一些解释。它不是一种技术、一个库或一个全新的框架。它只是几个(2000 LOC)帮助方法的一个方便的名称,可让您编写此类查询。只需 download their source(如果您没有安装 VS 示例)并在您的代码中使用它们。

    【讨论】:

    • 但是我需要从数据库 SQL Server 中检索数据
    • 不幸的是,Dynamic Linq 多年来一直没有更新,这就是我不考虑推荐它的原因。
    • @Romoku "Dynamic LINQ" 只是构建在 standard LINQ 实现之上的少数帮助类的 name没有什么可更新的...
    • 它正在通过社区(非官方)更新 - 位于此处:github.com/kahanu/System.Linq.Dynamic - 但@AdrianoRepetti 是正确的,确实没有太多要更新的,它只是标准之上的一个很好的层linq.
    • Tnx @TravisWhidden,不错的链接:他还添加了一些 IMO 在原始代码中缺少的示例(作为单元测试)。我更新了指向该仓库的答案!
    【解决方案2】:

    以下方法适用于 LINQ to Objects 以及 LINQ to EF / NHibernate / 等。
    它创建一个与作为字符串传递的列/属性相对应的表达式,并将该表达式传递给GroupBy

    private static Expression<Func<Menu, string>> GetGroupKey(string property)
    {
        var parameter = Expression.Parameter(typeof(Menu));
        var body = Expression.Property(parameter, property);
        return Expression.Lambda<Func<Menu, string>>(body, parameter);
    }
    

    与基于IQueryable&lt;T&gt; 的数据源一起使用:

    context.Menus.GroupBy(GetGroupKey(columnName));
    

    与基于IEnumerable&lt;T&gt; 的数据源一起使用:

    list.GroupBy(GetGroupKey(columnName).Compile());
    

    顺便说一句:您的方法的返回类型应该是IEnumerable&lt;IGrouping&lt;string, Menu&gt;&gt;,因为IGrouping&lt;string, Menu&gt; 已经意味着每个键可以有多个 Menu 实例。

    【讨论】:

    • 当它是“动态”时怎么办,即我不知道它是“菜单”?我不能做 typeof(dynamic) ,如果我做 typeof(object) 那么它会抱怨 System.Object 上不存在属性。
    • 您需要传递一个实例并在其上调用 GetType
    • 我有一袋 Expando 对象,它们只是字符串的键值对。我应该在这里传递什么实例。没有什么具体的
    • 请提出一个新问题。这超出了 cmets 的范围
    • 但基本上,ExpandoObject 实现了IDictionary&lt;string, object&gt;,所以你应该能够像这样简单地做一些事情:listOfYourExpandoObjects.GroupBy(x =&gt; x[columnName])
    【解决方案3】:

    最简单的方法:

    if(columnName == "MextText")
    {
        return list.GroupBy(x => x.MenuText);
    }
    
    if(columnName == "RoleName")
    {
        return list.GroupBy(x => x.RoleName);
    }
    
    if(columnName == "ActionName")
    {
        return list.GroupBy(x => x.ActionName);
    }
    
    return list.GroupBy(x => x.MenuText);
    

    您也可以使用表达式树。

    private static Expression<Func<Menu, string>> GetColumnName(string property)
    {
        var menu = Expression.Parameter(typeof(Menu), "menu");
        var menuProperty = Expression.PropertyOrField(menu, property);
        var lambda = Expression.Lambda<Func<Menu, string>>(menuProperty, menu);
    
        return lambda;
    }
    
    return list.GroupBy(GetColumnName(columnName).Compile());
    

    这将产生一个 lambda menu =&gt; menu.&lt;PropertyName&gt;

    但是在类变得臃肿之前并没有太大的区别。

    【讨论】:

    • 但这是一个示例,我正在寻找适用于任何模型的通用解决方案
    • 我喜欢 Romoku 总是快速给出完整答案
    【解决方案4】:

    我已经按照 Adriano 的建议使用 Dynamic Linq 完成了这项工作

    public static IEnumerable<IGrouping<object, TElement>> GroupByMany<TElement>(
            this IEnumerable<TElement> elements, params string[] groupSelectors)
        {
            var selectors = new List<Func<TElement, object>>(groupSelectors.Length);
            selectors.AddRange(groupSelectors.Select(selector => DynamicExpression.ParseLambda(typeof (TElement), typeof (object), selector)).Select(l => (Func<TElement, object>) l.Compile()));
            return elements.GroupByMany(selectors.ToArray());
        }
    
    public static IEnumerable<IGrouping<object, TElement>> GroupByMany<TElement>(
            this IEnumerable<TElement> elements, params Func<TElement, object>[] groupSelectors)
        {
            if (groupSelectors.Length > 0)
            {
                Func<TElement, object> selector = groupSelectors.First();
                return elements.GroupBy(selector);
            }
            return null;
        }
    

    【讨论】:

    • 在您的第二个 GroupByMany 方法中,您有 groupSelectors.First();因此分组仅在传递给第一个 GroupByMany 方法的 params 数组的字符串属性上执行。所以,我不明白..
    【解决方案5】:

    您的解决方案对于任何模型都很容易实现,我只是将其设为通用的。

    public static Expression<Func<TElement, string>> GetColumnName<TElement>(string property)
        {
            var menu = Expression.Parameter(typeof(TElement), "groupCol");
            var menuProperty = Expression.PropertyOrField(menu, property);
            var lambda = Expression.Lambda<Func<TElement, string>>(menuProperty, menu);
            return lambda;
        }
    

    下面这样称呼

    _unitOfWork.MenuRepository.Get().GroupBy(LinqExtensions.GetColumnName<Menu>("MenuText").Compile());
    

    非常感谢您的帮助。

    【讨论】:

      猜你喜欢
      • 2014-12-09
      • 2012-10-04
      • 2013-01-23
      • 2021-04-30
      • 2011-01-27
      • 2015-02-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多