Raggruppamento LINQ in modo dinamico

Ho un elenco di classi di record, quindi l’utente può scegliere di raggruppare dynamicmente le righe in base al nome della proprietà. Ad esempio MenuText , RoleName o ActionName . Quindi devo eseguire il raggruppamento, quindi ho bisogno di un metodo generico per gestire il raggruppamento passando il nome della colonna.

Esempio :

 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 } } 

Se non stai lavorando con un database puoi usare Reflection:

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

Usato come:

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

Questa è una soluzione piuttosto grezza, un modo migliore dovrebbe essere l’uso di LINQ dinamico :

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

EDIT Dynamic LINQ potrebbe aver bisogno di alcune spiegazioni. Non è una tecnologia, una biblioteca o un nuovo quadro. È solo un nome conveniente per un paio (2000 LOC) di metodi di aiuto che ti permettono di scrivere tali query. Basta scaricare la loro fonte (se non hai installato campioni VS) e usarli nel tuo codice.

Modo più semplice:

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

Puoi anche usare alberi di espressione.

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

Questo produrrà un menu => menu. lambda menu => menu. .

Ma non c’è davvero molta differenza fino a quando la class non diventa gonfia.

Il seguente approccio funzionerebbe con LINQ su oggetti e con LINQ su EF / NHibernate / ecc.
Crea un’espressione che corrisponde alla colonna / proprietà passata come stringa e passa quell’espressione 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); } 

Utilizzo con origini dati basate su IQueryable :

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

Utilizzo con origini dati IEnumerable :

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

BTW: Il tipo di restituzione del metodo deve essere IEnumerable> , poiché un IGrouping indica già che possono esserci più istanze Menu per chiave.

Ho fatto questo con Dynamic Linq come suggerito da 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; } 

La soluzione è semplice da implementare per qualsiasi modello, l’ho appena creata generica.

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

così chiamato come sotto

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

Grazie mille per l’aiuto.