表达式树组和使用任何类型的列选择

问题描述 投票:1回答:1

我正在使用下面优秀线程的代码变体,使用自定义列列从任何给定数据源动态构建树视图结构。

Expression.Call GroupBy then Select and Count()?

我的代码是此解决方案中提供的代码的递归变体:

public static List<MenuItem> GroupBySelector<TSource>(List<TSource> source, List<string> columns, int entry)
{
    string column = columns[entry];

    IQueryable<TSource> query = source.AsQueryable();

    int nextEntry = entry + 1;

    if (source == null) throw new ArgumentNullException("source");
    if (column == null) throw new ArgumentNullException("column");
    if (column.Length == 0) throw new ArgumentException("column");

    var param = Expression.Parameter(typeof(TSource));
    var prop = Expression.Property(param, column);

    Expression<Func<TSource, string>> keySelector = Expression.Lambda<Func<TSource, string>>
    (
        prop,
        param
    );

    if (columns.Count == nextEntry)
        return query.GroupBy(keySelector).Select(p => new MenuItem { Key = p.Key, Count = p.Count() }).ToList();
    else
        return query.GroupBy(keySelector)
          .Select(p => new MenuItem { Key = p.Key, Count = p.Count(), Items = GroupBySelector<TSource>(p.ToList(), columns, nextEntry) }).ToList();
}

虽然这适用于字符串类型的列;我将如何动态支持任何(原始)类型的列?包括包含不同类型的列列表 - 例如Name(字符串),Age(int)等?

c# linq lambda expression-trees
1个回答
0
投票

嗯,这是可能的,虽然相当模糊。一旦你开始使用运行时类型信息,它往往会向外传播,直到你在运行时编译代码,幸运的是,使用Expression并不是很难,但它绝对是一个可以在C#中改进的领域。

这是我的代码:

public static class MenuItemTree {
    public static MethodInfo GetGenericMethod(this Type type, string name, Type[] generic_type_args, Type[] param_types) {
        foreach (MethodInfo m in type.GetMethods())
            if (m.Name == name) {
                var pa = m.GetParameters();
                if (pa.Length == param_types.Length) {
                    var c = m.MakeGenericMethod(generic_type_args);
                    if (c.GetParameters().Select(p => p.ParameterType).SequenceEqual(param_types))
                        return c;
                }
            }
        return null;
    }

    static MethodInfo GetGroupByMethodStatically<TElement, TKey>()
        => typeof(Enumerable).GetGenericMethod("GroupBy", new[] { typeof(TElement), typeof(TKey) }, new[] { typeof(IEnumerable<TElement>), typeof(Func<TElement, TKey>) });

    static MethodInfo GetEnumerableMethod(string methodName, Type TTElement, Type TTKey) {
        var TIE = typeof(IEnumerable<>).MakeGenericType(TTElement);
        var TF = typeof(Func<,>).MakeGenericType(TTElement, TTKey);
        return typeof(Enumerable).GetGenericMethod(methodName, new[] { TTElement, TTKey }, new[] { TIE, TF });
    }

    static MethodInfo GetEnumerableMethod(string methodName, Type TTElement) {
        var TIE = typeof(IEnumerable<>).MakeGenericType(TTElement);
        return typeof(Enumerable).GetGenericMethod(methodName, new[] { TTElement }, new[] { TIE });
    }

    public static List<MenuItem> GroupBySelector<TElement>(IEnumerable<TElement> source, IList<string> columnNames, int entry = 0) {
        if (source == null) throw new ArgumentNullException(nameof(source));

        string columnName = columnNames[entry];

        if (columnName == null) throw new ArgumentNullException(nameof(columnName));
        if (columnName.Length == 0) throw new ArgumentException(nameof(columnName));

        int nextEntry = entry + 1;

        var TTElement = typeof(TElement);
        var TIETElement = typeof(IEnumerable<TElement>);

        var keyParm = Expression.Parameter(TTElement);
        var prop = Expression.Property(keyParm, columnName);

        var parm = Expression.Parameter(TIETElement, "p");
        var gbmi = GetEnumerableMethod("GroupBy", TTElement, prop.Type);
        var gbExpr = Expression.Lambda(prop, keyParm);
        var body = Expression.Call(gbmi, parm, gbExpr);

        var TSelectInput = typeof(IGrouping<,>).MakeGenericType(prop.Type, TTElement);
        var separm = Expression.Parameter(TSelectInput, "p");

        var miKey = typeof(MenuItem).GetMember("Key").Single();
        var miCount = typeof(MenuItem).GetMember("Count").Single();
        var miItems = typeof(MenuItem).GetMember("Items").Single();

        Expression sepKey = Expression.Property(separm, "Key");
        if (sepKey.Type != typeof(string)) {
            var ctmi = sepKey.Type.GetMethod("ToString", Type.EmptyTypes);
            sepKey = Expression.Call(sepKey, ctmi);
        }
        var countmi = GetEnumerableMethod("Count", TTElement);
        var sepCount = Expression.Call(countmi, separm);

        var gbsmi = GetGenericMethod(MethodBase.GetCurrentMethod().DeclaringType, "GroupBySelector", new[] { TTElement }, new[] { TIETElement, typeof(IList<string>), typeof(int) });
        var sepItems = Expression.Call(gbsmi, separm, Expression.Constant(columnNames), Expression.Constant(nextEntry));

        var senew = Expression.New(typeof(MenuItem));

        var se1body = Expression.MemberInit(senew, new[] {
                        Expression.Bind(miKey, sepKey),
                        Expression.Bind(miCount, sepCount),
                        Expression.Bind(miItems, sepItems)
                    });
        var se1 = Expression.Lambda(se1body, separm);

        var se2body = Expression.MemberInit(senew, new[] {
                        Expression.Bind(miKey, sepKey),
                        Expression.Bind(miCount, sepCount)
                    });
        var se2 = Expression.Lambda(se2body, separm);

        var smi = GetEnumerableMethod("Select", TSelectInput, typeof(MenuItem));
        body = Expression.Call(smi, body, (nextEntry < columnNames.Count) ? se1: se2);

        var lmi = GetEnumerableMethod("ToList", typeof(MenuItem));
        body = Expression.Call(lmi, body);

        var returnFunc = Expression.Lambda<Func<IEnumerable<TElement>, List<MenuItem>>>(body, parm).Compile();
        return returnFunc(source);
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.