Linq - 动态GroupBy与IEnumerable <t> </t>

时间:2011-10-20 01:59:53

标签: c# linq dynamic ienumerable

我有一个IEnumerable<Transaction>的集合。 Transaction有几个属性,如TransactionId(Int64),PaymentMethod(string)和TransactionDate(DateTime)

我希望能够在运行时根据用户决定使用的分组字段动态完成此transactions.GroupBy(x => x.PaymentMethod)

我在dtb的答案中找到了我正在寻找的大部分答案Linq GroupBy - how to specify the grouping key at runtime?

这很有效:

        var arg = Expression.Parameter( typeof( Transaction ), "transaction" );
        var body = Expression.Property( arg, "PaymentMethod" );
        var lambda = Expression.Lambda<Func<Transaction, string>>( body, arg );
        var keySelector = lambda.Compile();

        var groups = transactions.GroupBy( keySelector );

除了我不知道Expression.Lambda<Func<Transaction, string>>中Func的返回类型的类型。它是这个例子中的字符串,但它可能是Int64,decimal,DateTime等。我不能使用Object作为返回类型,因为我可能有值类型。

我一直在阅读很多SO帖子,其中大部分似乎都适用于IQueryable和LinqToSQL。

使用Expression类似乎是一种很好的方法,但是当我在编译时不知道group参数的名称或数据类型时,有没有办法呢?

我感谢任何朝着正确方向的推动。

编辑:

使用下面的Polity解决方案,我创建了一个扩展方法来执行我一直在尝试做的事情:

    public static IEnumerable<IGrouping<object, T>> GroupBy<T>( this IEnumerable<T> items, string groupByProperty )
    {

        var arg = Expression.Parameter( typeof(T), "item" );
        var body = Expression.Convert( Expression.Property( arg, groupByProperty ), typeof( object ) );
        var lambda = Expression.Lambda<Func<T, object>>( body, arg );
        var keySelector = lambda.Compile();

        var groups = items.GroupBy( keySelector );
        return groups;
    } 

感谢Polity和所有回答的人!

3 个答案:

答案 0 :(得分:4)

跟进ojlovecd的回答。

根据要求他在运行时需要功能的人。泛型和运行时不是最简单的组合。这是没有问题的,因为您可以将返回值威胁为一个对象,这使得ojlovecd提供的方法的非泛型变体类似于:

static IEnumerable<IGrouping<object,Transaction>> GroupBy(string propName) 
{ 
    List<Transaction> transactions = new List<Transaction>  
    { 
        new Transaction{ PaymentMethod="AA", SomeDateTime=DateTime.Now.AddDays(-1), SomeDecimal=1.2M, SomeInt64=1000}, 
        new Transaction{ PaymentMethod="BB", SomeDateTime=DateTime.Now.AddDays(-2), SomeDecimal=3.4M, SomeInt64=2000}, 
        new Transaction{ PaymentMethod="AA", SomeDateTime=DateTime.Now.AddDays(-1), SomeDecimal=3.4M, SomeInt64=3000}, 
        new Transaction{ PaymentMethod="CC", SomeDateTime=DateTime.Now.AddDays(2), SomeDecimal=5.6M, SomeInt64=1000}, 
    }; 
    var arg = Expression.Parameter(typeof(Transaction), "transaction"); 
    var body = Expression.Convert(Expression.Property(arg, propName), typeof(object)); 
    var lambda = Expression.Lambda<Func<Transaction, object>>(body, arg); 
    var keySelector = lambda.Compile(); 

    var groups = transactions.GroupBy(keySelector); 
    return groups; 
} 

答案 1 :(得分:1)

以下代码是我的意思,希望它们有用:

static void Main(string[] args)
{

    var query = GroupBy<string>("PaymentMethod");
    foreach (var group in query)
        Console.WriteLine(group.Key + "," + group.Count());
    var query2 = GroupBy<long>("SomeInt64");
    foreach (var group in query2)
        Console.WriteLine(group.Key + "," + group.Count());
}

static IEnumerable<IGrouping<T,Transaction>> GroupBy<T>(string propName)
{
    List<Transaction> transactions = new List<Transaction> 
    {
        new Transaction{ PaymentMethod="AA", SomeDateTime=DateTime.Now.AddDays(-1), SomeDecimal=1.2M, SomeInt64=1000},
        new Transaction{ PaymentMethod="BB", SomeDateTime=DateTime.Now.AddDays(-2), SomeDecimal=3.4M, SomeInt64=2000},
        new Transaction{ PaymentMethod="AA", SomeDateTime=DateTime.Now.AddDays(-1), SomeDecimal=3.4M, SomeInt64=3000},
        new Transaction{ PaymentMethod="CC", SomeDateTime=DateTime.Now.AddDays(2), SomeDecimal=5.6M, SomeInt64=1000},
    };
    var arg = Expression.Parameter(typeof(Transaction), "transaction");
    var body = Expression.Property(arg, propName);
    var lambda = Expression.Lambda<Func<Transaction, T>>(body, arg);
    var keySelector = lambda.Compile();

    var groups = transactions.GroupBy(keySelector);
    return groups;
}

    class Transaction
    {
        public string PaymentMethod { get; set; }
        public Int64 SomeInt64 { get; set; }
        public decimal SomeDecimal { get; set; }
        public DateTime SomeDateTime { get; set; }
    }

答案 2 :(得分:1)

如果在编译时不知道目标类型,则必须构造委托类型并使用Expression.Call()并最终使用GroupBy执行DynamicInvoke() - 这对我有用:

var arg = Expression.Parameter(typeof(Transaction), "transaction");
var body = Expression.Property(arg, "PaymentMethod");

var delegateType = typeof(Func<,>).MakeGenericType(typeof(Transaction), body.Type);
var lambda = Expression.Lambda(delegateType, body, arg);
var source = Expression.Parameter(typeof(IEnumerable<Transaction>), "source");
var groupByExpression = Expression.Call(typeof(Enumerable), "GroupBy", 
                                        new Type[] { typeof(Transaction), body.Type }, 
                                        source, lambda);
var groupByLambda = Expression.Lambda(groupByExpression, source).Compile();

var groups = groupByLambda.DynamicInvoke(transactions);

由于此时你不能使用任何其他Linq扩展方法而不将它们转换为表达式(至少据我所知),这种好处是值得怀疑的,所以我个人可能会选择其中一个其他选项