web-dev-qa-db-ja.com

動的なLINQグループ化

レコードのクラスリストがあるので、ユーザーはプロパティ名で行を動的にグループ化することを選択できます。たとえば、MenuTextRoleName、または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 
  }
}
11
ineffable p

データベースを使用していない場合は、Reflectionを使用できます。

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 [〜#〜]DynamicLINQには説明が必要な場合があります。これは、テクノロジー、ライブラリ、またはまったく新しいフレームワークではありません。これは、このようなクエリを記述できるヘルパーメソッドのいくつか(2000 LOC)の便利な名前です。ただ ソースをダウンロード (VSサンプルがインストールされていない場合)そしてコードでそれらを使用してください。

19
Adriano Repetti

次のアプローチは、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インスタンスが存在する可能性があることをすでに意味しているためです。

3
Daniel Hilgarth

最も簡単な方法:

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

これにより、ラムダmenu => menu.<PropertyName>が生成されます。

しかし、クラスが肥大化するまで、それほど大きな違いはありません。

3
Dustin Kingen

Adrianoが提案したように、DynamicLinqを使用してこれを実行しました

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;
    }
2
ineffable p

あなたのソリューションはどのモデルにも簡単に実装できます。私はそれを一般的なものにしました。

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

助けてくれてありがとう。

0
ineffable p