简体   繁体   English

Linq - 动态GroupBy与IEnumerable <T>

[英]Linq - Dynamic GroupBy with IEnumerable<T>

I have a collection that is IEnumerable<Transaction> . 我有一个IEnumerable<Transaction>的集合。 Transaction has several properties such as TransactionId (Int64), PaymentMethod(string) and TransactionDate(DateTime) Transaction有几个属性,如TransactionId(Int64),PaymentMethod(string)和TransactionDate(DateTime)

I'd like to be able to accomplish this transactions.GroupBy(x => x.PaymentMethod) dynamically at run time based on whatever grouping field the user has decided to use. 我希望能够在运行时根据用户决定使用的任何分组字段动态地完成此transactions.GroupBy(x => x.PaymentMethod)

I found most of the answer I'm looking for in dtb's answer here Linq GroupBy - how to specify the grouping key at runtime? 我在dtb的答案中找到了大部分的答案Linq GroupBy - 如何在运行时指定分组键?

This works well: 这很好用:

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

Except that I don't know the type of the return type of the Func in Expression.Lambda<Func<Transaction, string>> . 除了我不知道Expression.Lambda<Func<Transaction, string>>的返回类型的类型。 It's string in this example, but it might be Int64, decimal, DateTime, etc. I can't use Object as the return type because I might have value types. 它是这个例子中的字符串,但它可能是Int64,decimal,DateTime等。我不能使用Object作为返回类型,因为我可能有值类型。

I've been reading lots of SO posts and most of them seem to apply to IQueryable and LinqToSQL. 我一直在阅读很多SO帖子,其中大部分似乎都适用于IQueryable和LinqToSQL。

Using the Expression class seems like a good way to accomplish this, but is there a way to do it when I don't know either the name or datatype of my group parameter at compile time? 使用Expression类似乎是一种很好的方法来实现这一点,但是当我在编译时不知道我的group参数的名称或数据类型时,有没有办法呢?

I appreciate any nudge in the right direction. 我很欣赏任何朝着正确方向的推动。

Edit: 编辑:

Using Polity's solution below, I created an extension method that does what I've been trying to do: 使用下面的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;
    } 

Thanks to Polity and everyone who answered! 感谢Polity和所有回答的人!

Following up on the answer of ojlovecd. 跟进ojlovecd的回答。

According to the one asking he needs functionality at runtime. 根据要求他在运行时需要功能的人。 Generics and Runtime arent the easiest mix. 泛型和运行时不是最简单的组合。 That is no problem though since you can just threat the return value as an object which makes the non-generic variant of the method provided by ojlovecd something like: 这是没有问题的,因为你只能威胁返回值作为一个对象,这使得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; 
} 

the following codes are what I mean, hoping they are helpful: 以下代码是我的意思,希望它们有用:

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

You will have to construct the delegate type and use Expression.Call() and finally execute the GroupBy using DynamicInvoke() if you do not know the target type at compile time - this works for me: 如果在编译时不知道目标类型,则必须构造委托类型并使用Expression.Call()并最终使用DynamicInvoke()执行GroupBy - 这对我有用:

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

Since at this point you cannot use any other Linq extension methods without converting them to expressions as well (at least as far as I understand it) the benefit is doubtful, so I personally probably would go with one of the other options presented. 因为在这一点上你不能使用任何其他Linq扩展方法而不将它们转换为表达式(至少据我所知),这种好处是值得怀疑的,所以我个人可能会选择其中一个其他选项。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM