LINQ - Написание метода расширения, чтобы получить строку с максимальным значением для каждой группы - PullRequest
5 голосов
/ 19 августа 2011

Моему приложению часто нужно сгруппировать таблицу, а затем вернуть строку с максимальным значением для этой группы. Это довольно легко сделать в LINQ:

myTable.GroupBy(r => r.FieldToGroupBy)
.Select(r => r.Max(s => s.FieldToMaximize))
.Join(
    myTable,
    r => r,
    r => r.FieldToMaximize,
    (o, i) => i)

Теперь предположим, что я хочу абстрагировать это в свой собственный метод. Я пытался написать это:

public static IQueryable<TSource>
SelectMax<TSource, TGroupKey, TMaxKey>(
    this IQueryable<TSource> source,
    Expression<Func<TSource, TGroupKey>> groupKeySelector,
    Expression<Func<TSource, TMaxKey>> maxKeySelector)
    where TMaxKey : IComparable
{
    return source
        .GroupBy(groupKeySelector)
        .Join(
            source,
            g => g.Max(maxKeySelector),
            r => maxKeySelector(r),
                (o, i) => i);
}

К сожалению, это не компилируется: maxKeySelector является выражением (поэтому вы не можете вызвать его на r и даже не передать его Max. Поэтому я попытался переписать, сделав maxKeySelector функцией, а не выражением:

public static IQueryable<TSource>
SelectMax<TSource, TGroupKey, TMaxKey>(
    this IQueryable<TSource> source,
    Expression<Func<TSource, TGroupKey>> groupKeySelector,
    Func<TSource, TMaxKey> maxKeySelector)
    where TMaxKey : IComparable
{
    return source
        .GroupBy(groupKeySelector)
        .Join(
            source,
            g => g.Max(maxKeySelector),
            r => maxKeySelector(r),
                (o, i) => i);
}

Теперь это компилируется. Но происходит сбой во время выполнения: «Неподдерживаемая перегрузка используется для оператора запроса« Макс »». Это то, на чем я застрял: мне нужно найти правильный способ передачи maxKeySelector в Max ().

Есть предложения? Я использую LINQ to SQL, что, кажется, имеет значение.

Ответы [ 2 ]

4 голосов
/ 19 августа 2011

Очень интересно. Иногда «динамический» может стоить вам больше в простой разработке и исполнении во время выполнения, чем это стоит (ИМХО). Тем не менее, вот самое простое:

public static IQueryable<Item> _GroupMaxs(this IQueryable<Item> list)
{
    return list.GroupBy(x => x.Family)
        .Select(g => g.OrderByDescending(x => x.Value).First());
}

И вот наиболее динамичный подход:

public static IQueryable<T> _GroupMaxs<T, TGroupCol, TValueCol>
    (this IQueryable<T> list, string groupColName, string valueColName)
{
    // (x => x.groupColName)
    var _GroupByPropInfo = typeof(T).GetProperty(groupColName);
    var _GroupByParameter = Expression.Parameter(typeof(T), "x");
    var _GroupByProperty = Expression
            .Property(_GroupByParameter, _GroupByPropInfo);
    var _GroupByLambda = Expression.Lambda<Func<T, TGroupCol>>
        (_GroupByProperty, new ParameterExpression[] { _GroupByParameter });

    // (x => x.valueColName)
    var _SelectParameter = Expression.Parameter(typeof(T), "x");
    var _SelectProperty = Expression
            .Property(_SelectParameter, valueColName);
    var _SelectLambda = Expression.Lambda<Func<T, TValueCol>>
        (_SelectProperty, new ParameterExpression[] { _SelectParameter });

    // return list.GroupBy(x => x.groupColName)
    //   .Select(g => g.OrderByDescending(x => x.valueColName).First());
    return list.GroupBy(_GroupByLambda)
        .Select(g => g.OrderByDescending(_SelectLambda.Compile()).First());
}

Как вы можете видеть, мои методы расширения предшествуют подчеркиванием. Вам не нужно делать это, конечно. Просто возьми общую идею и беги с ней.

Вы бы назвали это так:

public class Item
{
    public string Family { get; set; }
    public int Value { get; set; }
}

foreach (Item item in _List
        .AsQueryable()._GroupMaxs<Item, String, int>("Family", "Value"))
    Console.WriteLine("{0}:{1}", item.Family, item.Value);

Удачи!

4 голосов
/ 19 августа 2011

Прежде всего, я хотел бы отметить, что то, что вы пытаетесь сделать, даже проще, чем вы думаете в LINQ:

myTable.GroupBy(r => r.FieldToGroupBy)
    .Select(g => g.OrderByDescending(r => r.FieldToMaximize).FirstOrDefault())

... что должно сделать нашу жизнь немного легче для второй части:

public static IQueryable<TSource>
SelectMax<TSource, TGroupKey, TMaxKey>(
    this IQueryable<TSource> source,
    Expression<Func<TSource, TGroupKey>> groupKeySelector,
    Expression<Func<TSource, TMaxKey>> maxKeySelector)
    where TMaxKey : IComparable
{
    return source
        .GroupBy(groupKeySelector)
        .Select(g => g.AsQueryable().OrderBy(maxKeySelector).FirstOrDefault());
}

Ключ в том, что, сделав вашу группу IQueryable, вы открываете новый набор методов LINQ, которые могут принимать фактические выражения вместо Func s. Это должно быть совместимо с большинством стандартных поставщиков LINQ.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...