Linq - динамический GroupBy с IEnumerable <T> - PullRequest
4 голосов
/ 20 октября 2011

У меня есть коллекция, которая IEnumerable<Transaction>.Транзакция имеет несколько свойств, таких как TransactionId (Int64), PaymentMethod (строка) и TransactionDate (DateTime)

Я хотел бы иметь возможность выполнить это transactions.GroupBy(x => x.PaymentMethod) динамически во время выполнения на основе любого поля группировки пользователярешил использовать.

Я нашел большую часть ответа, который я ищу, в ответе dtb здесь Linq GroupBy - как указать ключ группировки во время выполнения?

Это хорошо работает:

        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, десятичное число, DateTime и т. Д. Я не могу использовать Object в качестве возвращаемого типа, потому что у меня могут быть типы значений.и большинство из них, кажется, применимы к IQueryable и LinqToSQL.

Использование класса Expression кажется хорошим способом сделать это, но есть ли способ сделать это, когда я не знаю ни имени, ни типа данных параметра моей группы во время компиляции?

Я ценю любое движение в правильном направлении.

Редактировать:

Используя решение 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 ]

4 голосов
/ 20 октября 2011

В ответ на ответ ojlovecd.

Согласно одному из вопросов, ему нужна функциональность во время выполнения. Generics и Runtime не самая простая смесь. Это не проблема, так как вы можете просто угрожать возвращаемому значению как объекту, что делает неуниверсальный вариант метода, предоставляемого 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 голос
/ 20 октября 2011

Вам нужно будет создать тип делегата и использовать 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, не преобразовав их также в выражения (по крайней мере, насколько я понимаю), выгода сомнительна, поэтому я лично, вероятно, согласился бы с одним из других представленных вариантов .

1 голос
/ 20 октября 2011

я имею в виду следующие коды, надеясь, что они полезны:

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