2011-08-18 12 views
5

私のアプリケーションでは、テーブルをグループ化してから、そのグループの最大値を持つ行を返さなければならないことがよくあります。これは、LINQで行うのは非常に簡単です: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でそれを呼び出すことはできませんし、あなたもマックスにそれを渡すことができない表現ですだから私は書き換えてみました、 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を使用すると、違いがあるようです。すべての

答えて

4

まず、私はあなたが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することによって、あなたはむしろFunc秒を取るよりも、実際の式を取ることができますLINQメソッドの新しいセットを開くということです。これは、ほとんどの標準LINQプロバイダと互換性があります。

+0

これは間違いなく簡潔ですが、残念ながらうまくいきません。これまでと同じエラーが発生します。「クエリ演算子 'OrderBy'にサポートされていないオーバーロードが使用されています。 – ctkrohn

+0

@ctkrohn:どのLINQプロバイダを使用していますか? LINQ to Entities ....とLINQ to Objectsで私はうまく動いています。 – StriplingWarrior

+0

これはLINQ to SQLです。 – ctkrohn

4

非常に興味深い。時には "動的"は、価値があるよりもずっと高い開発とランタイム実行でより多くのコストをかけることができます(IMHO)。

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

+0

これは賢明かつ役立つように見えますが、私の目的のためにおそらく過剰です。私は他の方法を除外しなければならない場合に備えて、このテクニックをブックマークしますが、今のところコードを一行で記述する方が簡単でしょう。 – ctkrohn

+0

@ctkrohn、welp、あなたの質問にそのような方法を求めているのはあなたには恥ずかしいです。もし私が選んでいたら、私はそれをインラインで書いたでしょう。しかし、私はあなたの元の質問にこれ以上の優雅な答えが表示されません。 C'est la vie、そう思う。 –

+0

私が提供するにはあまりにも怠惰だったコードを書く時間を取って+1。 – StriplingWarrior

関連する問題