LINQ动态分组

14

我有一份记录的班级列表,因此用户可以选择按属性名称动态分组行。例如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 
  }
}
5个回答

25

如果您没有使用数据库,可以使用反射:

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

编辑 动态 LINQ 可能需要一些解释。它不是一种技术、库或全新的框架。它只是一个方便的名称,用于描述一组(2000 行代码)帮助方法,让您可以编写此类查询。只需下载其源代码(如果您没有安装 VS 示例),并在您的代码中使用它们即可。


请问您是否有动态 LINQ 的示例可以提供参考? - ineffable p
1
遗憾的是,动态 Linq 已经多年没有更新了,这就是为什么我不会考虑推荐它的原因。 - Dustin Kingen
3
“动态LINQ”只是在标准LINQ实现的基础上构建的几个帮助类的名称,没有任何需要更新的内容…… - Adriano Repetti
1
它有点通过社区(非官方地)进行更新 - 位于此处:https://github.com/kahanu/System.Linq.Dynamic - 但@AdrianoRepetti是正确的,实际上没有太多需要更新的内容,它只是标准linq之上的一个不错的层。 - TravisWhidden
1
谢谢@TravisWhidden,好链接:他还添加了一些例子(作为单元测试),这在原始代码中我认为是缺少的。我更新了答案以指向那个仓库! - Adriano Repetti
显示剩余3条评论

7
以下方法适用于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<T>的数据源一起使用:

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

使用基于 IEnumerable<T> 的数据源:

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

顺便提一下:您的方法返回类型应该是IEnumerable<IGrouping<string, Menu>>,因为IGrouping<string, Menu>已经意味着每个键可以有多个Menu实例。


当它是“动态”的时候,该怎么办?也就是说,我不知道它是“菜单”?我不能使用typeof(dynamic),如果我使用typeof(object),那么它会抱怨在System.Object上不存在属性。 - Angshuman Agarwal
你需要传递一个实例并调用它的GetType方法。 - Daniel Hilgarth
我有一个Expando对象的集合,它只是一组键值对字符串。我应该在这里传递什么实例?没有具体的东西。 - Angshuman Agarwal
请提出一个新问题。这超出了评论的范围。 - Daniel Hilgarth
但基本上,ExpandoObject实现了IDictionary<string, object>,因此您应该能够执行像这样简单的操作:listOfYourExpandoObjects.GroupBy(x => x[columnName]) - Daniel Hilgarth

5

最简单的方法:

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 => menu.<PropertyName>

但在类变得臃肿之前,实际上没有太大的区别。


2
但这只是一个例子,我正在寻找一种通用的解决方案来实现任何模型。 - ineffable p
我喜欢Romoku总是能快速给出完整的答案。 - No Idea For Name

2
我已经按照Adriano的建议,使用动态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数组的字符串属性上执行。所以,我不明白其中的意义。 - Witcher

0

你的解决方案对于任何模型都很容易实现,我刚刚使它变得通用了。

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

非常感谢您的帮助。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接