C# LINQ动态分组

C# LINQ动态分组,c#,.net,linq,group-by,C#,.net,Linq,Group By,我有一个记录的类列表,所以用户可以选择按属性名动态分组行。例如MenuText,RoleName或ActionName。然后我必须执行分组,所以我需要一个通用方法来通过传递列名来处理分组 例如: public class Menu { public string MenuText {get;set;} public string RoleName {get;set;} public string ActionName {get;set;} } public class Menus

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

例如:

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 
  }
}
公共类菜单
{
公共字符串MenuText{get;set;}
公共字符串RoleName{get;set;}
公共字符串ActionName{get;set;}
}
公共类菜单
{
var list=新列表();
添加(新菜单{MenuText=“abc”,RoleName=“Admin”,ActionName=“xyz”};
添加(新菜单{MenuText=“abc”,RoleName=“Admin”,ActionName=“xyz”};
添加(新菜单{MenuText=“abc1”,RoleName=“Admin1”,ActionName=“xyz1”};
添加(新菜单{MenuText=“abc1”,RoleName=“Admin1”,ActionName=“xyz1”};
///columnName:-菜单类的名称(MenuText或RoleName或ActionName)
公共IEnumerable组菜单(字符串列名称)
{
//在这里,我想按列名对菜单列表进行分组
}
}
最简单的方法:

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());
私有静态表达式GetColumnName(字符串属性)
{
变量菜单=表达式参数(类型(菜单),“菜单”);
var menuperty=Expression.PropertyOrField(菜单,属性);
var lambda=Expression.lambda(菜单属性,菜单);
返回lambda;
}
return list.GroupBy(GetColumnName(columnName.Compile());
这将产生一个lambda
菜单=>菜单。


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

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

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可能需要一些解释。它不是一种技术、一个库或一个全新的框架。它只是一个方便的名称,用于几个(2000 LOC)helpers方法,让您编写这样的查询。只是(如果您没有安装VS示例)并在代码中使用它们。

以下方法适用于LINQ to对象以及LINQ to EF/NHibernate/etc。
它创建一个表达式,该表达式对应于作为字符串传递的列/属性,并将该表达式传递给
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);
}
与基于
IEnumerable
的数据源一起使用:

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

顺便说一句:您的方法的返回类型应该是
IEnumerable
,因为
iGroup
已经意味着每个键可以有多个
菜单
实例。

我已经按照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;
    }
公共静态IEnumerable GroupByMany(
此IEnumerable元素、参数字符串[]groupSelectors)
{
变量选择器=新列表(groupSelectors.Length);
selectors.AddRange(groupSelectors.Select(selector=>DynamicExpression.ParseLambda(typeof(TElement)、typeof(object)、selector)).Select(l=>(Func)l.Compile());
返回elements.GroupByMany(selectors.ToArray());
}
公共静态IEnumerable GroupByMany(
这是IEnumerable元素,参数Func[]groupselector)
{
如果(groupSelectors.Length>0)
{
Func selector=groupSelectors.First();
返回元素.GroupBy(选择器);
}
返回null;
}

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

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;
    }
公共静态表达式GetColumnName(字符串属性)
{
变量菜单=表达式参数(typeof(TElement),“groupCol”);
var menuperty=Expression.PropertyOrField(菜单,属性);
var lambda=Expression.lambda(菜单属性,菜单);
返回lambda;
}
这就是所谓的下文

_unitOfWork.MenuRepository.Get().GroupBy(LinqExtensions.GetColumnName<Menu>("MenuText").Compile());
\u unitOfWork.MenuRepository.Get().GroupBy(LinqExtensions.GetColumnName(“MenuText”).Compile());

非常感谢您的帮助。但是,这是一个例子,我正在寻找一个通用的解决方案来实现任何模型,像RoMuKu总是有一个完整的答案FAST,但是我需要从数据库SQL Server中检索数据。不幸的是,动态LINQ多年来一直没有被更新,这就是为什么我不考虑推荐它。只是在标准LINQ实现之上构建的几个helpers类的名称。没有什么需要更新的…它是通过社区(非正式)进行更新的-位于此处:-但是@AdrianoRepetti是正确的,确实没有太多需要更新的,它只是标准linq.Tnx@Traviswhiden上的一个不错的层,很好的链接:他还添加了一些IMO在原始代码中缺少的示例(作为单元测试)。我更新了答案,指向那个repo!当它是“动态”的时候该怎么办,也就是说,我不知道它是“菜单”?我不能做typeof(动态)和如果我做typeof(对象)然后它会抱怨System.Object上不存在属性。你需要传递一个实例并在itI上调用GetType。我有一包Expando对象,它们只不过是一对键值字符串。我应该在这里传递什么实例。没有具体的内容。请问一个新问题。这超出了评论的范围,但基本上ExpandoObject实现了

IDictionary
,因此您应该能够做如下简单的事情:
listOfYourExpandoObjects.GroupBy(x=>x[columnName])
在第二个GroupByMany方法中,您有GroupSelector.First();因此,分组仅对传递到第一个GroupByMany方法的params数组的string属性执行。因此,我没有得到一个要点。。
_unitOfWork.MenuRepository.Get().GroupBy(LinqExtensions.GetColumnName<Menu>("MenuText").Compile());