LINQ Agrupamiento dinámicamente

Tengo una lista de clase de registros, por lo que el usuario puede seleccionar agrupar filas dinámicamente por nombre de propiedad. Por ejemplo MenuText , RoleName o ActionName . Luego tengo que ejecutar la agrupación, así que necesito un método genérico para manejar la agrupación al pasar el nombre de la columna.

Ejemplo:

 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(); 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>> GroupMenu(string columnName) { // Here I want to get group the list of menu by the columnName } } 

Si no está trabajando con una base de datos, puede usar Reflection:

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

Usado como:

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

Esta es una solución bastante cruda, una mejor manera debería ser utilizar Dynamic LINQ :

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

EDITAR Dynamic LINQ quizás necesite algunas explicaciones. No es una tecnología, una biblioteca o un marco nuevo. Es solo un nombre conveniente para un par (2000 LOC) de métodos de ayudantes que le permiten escribir tales consultas. Simplemente descargue su fuente (si no tiene instaladas muestras VS) y úselas en su código.

La manera más simple:

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

También podrías usar árboles de expresiones.

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

Esto producirá un menu => menu. lambda menu => menu. .

Pero no hay mucha diferencia hasta que la clase se hinche.

El siguiente enfoque funcionaría con LINQ to Objects así como también con LINQ to EF / NHibernate / etc.
Crea una expresión que corresponde a la columna / propiedad pasada como la cadena y pasa esa expresión a GroupBy :

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

Uso con fonts de datos basadas en IQueryable :

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

Uso con fonts de datos basadas en IEnumerable :

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

Por cierto: el tipo de devolución de su método debe ser IEnumerable> , porque un IGrouping ya significa que puede haber múltiples instancias de Menu por tecla.

He hecho esto con Dynamic Linq como lo sugirió Adriano

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

Su solución es simple de implementar para cualquier modelo, la he convertido en una genérica.

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

llamado así como a continuación

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

Muchas gracias por la ayuda.