2011-08-18 10 views
5

Moja aplikacja często musi zgrupować tabelę, a następnie zwrócić wiersz z maksymalną wartością dla tej grupy. Jest to łatwe do wykonania w LINQ:LINQ - Zapisywanie metody rozszerzenia w celu uzyskania wiersza z maksymalną wartością dla każdej grupy

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

Teraz przypuśćmy, że chcę to rozwinąć na własną metodę. Próbowałem pisząc w ten sposób:

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

Niestety nie kompiluje: maxKeySelector jest wyrazem (więc nie można nazwać go na badania, a nawet nie można przekazać go do Max Spróbowałem więc przepisywanie,. podejmowania maxKeySelector funkcję zamiast wyrażenia.

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

teraz kompiluje Ale to nie w czasie wykonywania: „Nieobsługiwane przeciążenia używanego do operatora zapytania«Max»” to jest to, co ja utknąłem na: muszę znajdź właściwy sposób przekazania maxKeySelector do Max().

Wszelkie sugestie? ing LINQ do SQL, co wydaje się mieć znaczenie.

Odpowiedz

4

Przede wszystkim chciałbym podkreślić, że to, co próbujesz zrobić, to nawet łatwiejsze niż myślisz w LINQ:

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

... co powinien uczynić nasze życie trochę łatwiejsze do drugiej części:

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

kluczem jest to, że poprzez swoją grupę IQueryable, otwierasz nowy zestaw metod LINQ, które mogą przyjmować rzeczywiste wyrażeń zamiast biorąc Func s. Powinno to być zgodne z większością standardowych dostawców LINQ.

+0

Cóż, jest to zdecydowanie bardziej zwięzłe, ale niestety nie działa. Dostaję prawie taki sam błąd jak poprzednio: "Nieobsługiwane przeciążenie używane dla operatora zapytania" OrderBy "". – ctkrohn

+0

@ctkrohn: Z jakiego dostawcy LINQ korzystasz? Działa to dobrze dla mnie na LINQ do Entities .... i LINQ to Objects. – StriplingWarrior

+0

Ten jest LINQ do SQL. – ctkrohn

4

Bardzo interesujące. Czasami "dynamiczny" może kosztować cię więcej w samym rozwoju i wykonywaniu zadań niż jest to warte (IMHO). Niemniej jednak, oto najprostszy:

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

A oto najbardziej dynamiczne podejście:

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

Jak widać, ja poprzedzać moje rozszerzenie metod z podkreślenia. Oczywiście nie musisz tego robić. Po prostu weź ogólny pomysł i używaj go.

Można by nazwać tak:

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

powodzenia!

+0

To wygląda sprytnie i pomocniczo, ale jest prawdopodobnie przesadą dla moich celów. Zakładam tę technikę na wypadek, gdybym musiał wziąć pod uwagę inne metody, ale na razie łatwiej jest po prostu napisać kod w linii. – ctkrohn

+0

@ctkrohn, welp, wstydź się, że pytasz o taką metodę w swoim pytaniu. Gdybym wybrał, napisałbym to również w tekście. Ale nie widzę bardziej eleganckiej odpowiedzi na twoje pierwotne pytanie, że to. C'est la vie, jak przypuszczam. –

+0

+1 za poświęcenie czasu na napisanie kodu, który byłem zbyt leniwy, aby zapewnić. – StriplingWarrior

Powiązane problemy