2011-10-20 19 views
4

我有一个集合,是IEnumerable<Transaction>。交易有几个属性,如TransactionId(Int64),PaymentMethod(字符串)和TransactionDate(日期时间)LINQ的 - 动态的GroupBy与IEnumerable的<T>

我希望能够在运行时动态完成这个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); 

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

我一直在阅读大量的SO职位,其中大部分似乎适用于IQueryable的和LinqToSQL。

使用表达式类好像做到这一点的好办法,但有没有办法做到这一点的时候,我不知道名称或在编译时我的组参数的数据类型?

我很欣赏任何在正确的方向微调。

编辑:

使用下面政体的解决方案,我创建了一个扩展方法做什么,我一直在努力做的事情:

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

由于政体和大家谁回答!

+0

你可以把这些代码放到一个通用的方法中,并将返回类型设置为通用参数的类型吗? – ojlovecd

+0

如果我理解你的问题,我不这么认为。我所拥有的几乎全部是用户选择作为组密钥的属性的字符串名称。我想我必须使用一些反射来获得类型。 –

+0

其中一位与我合作的人建议根据分组属性的类型创建一个switch语句,为所有值类型添加一个案例,并为它的类型添加默认使用对象。这会起作用,但我认为编码后需要淋浴。 –

回答

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

我早些时候尝试过。当分组值是一个引用类型时,它很好用,但不能将它用于值类型(如Int64)。你得到这个:System.ArgumentException:类型'System.Int64'的表达式不能用于返回类型'System.Object' –

+0

@Nathan Ratcliff - 我编辑代码有点包括从proprety到一个对象的Cast。这并不完美,因为我怀疑许多系统会理解这一点。 EF为一个不理解演员,但它适用于对象 – Polity

+0

谢谢,这对我很有用。我仍然在学习Expression的东西。 –

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

不幸的是,我没有编译时的类型。我所要的只是属性名称,我将不得不在运行时确定类型。 –

1

您必须构建委托类型和使用Expression.Call()并使用GroupByDynamicInvoke(),如果你不知道在编译时的目标类型最后执行 - 这对我的作品:

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的扩展方法,而不将其转换为表达式,以及(至少据我了解)的好处是值得怀疑的,所以我个人可能会选择其中一个选项。

+0

无论如何我都提高了 - 这是我以为我想要做的,但正如你指出的那样,除非你知道类型,否则你不能使用组。感谢你的回答。 –