我有一个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和所有回答的人!
答案 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扩展方法而不将它们转换为表达式(至少据我所知),这种好处是值得怀疑的,所以我个人可能会选择其中一个其他选项