4

次のような非常に単純なエンティティがあるとします。

public class TestGuy
{
    public virtual long Id {get;set;}
    public virtual string City {get;set;}
    public virtual int InterestingValue {get;set;}
    public virtual int OtherValue {get;set;}
}

この考案されたサンプル オブジェクトは、(Fluent を使用して) NHibernate でマップされ、正常に動作します。

いくつかのレポートを行う時間です。この例では、「testGuys」は、いくつかの条件が既に適用されている IQueryable です。

var byCity = testGuys
    .GroupBy(c => c.City)
    .Select(g => new { City = g.Key, Avg = g.Average(tg => tg.InterestingValue) });

これはうまくいきます。NHibernate Profiler では、正しい SQL が生成されていることを確認でき、結果は期待どおりです。

自分の成功に触発されて、もっと柔軟にしたいと思っています。ユーザーが OtherValue と InterestingValue の平均を取得できるように、構成可能にしたいと考えています。難しいことではありませんが、Average() の引数は Func のようです (この場合、値は int であるため)。簡単です。何らかの条件に基づいて Func を返すメソッドを作成し、それを引数として使用することはできませんか?

var fieldToAverageBy = GetAverageField(SomeEnum.Other);

private Func<TestGuy,int> GetAverageField(SomeEnum someCondition)
{
    switch(someCondition)
    {
        case SomeEnum.Interesting:
            return tg => tg.InterestingValue;
        case SomeEnum.Other:
            return tg => tg.OtherValue;
    }

    throw new InvalidOperationException("Not in my example!");
}

そして、他の場所では、これを行うことができます:

var byCity = testGuys
    .GroupBy(c => c.City)
    .Select(g => new { City = g.Key, Avg = g.Average(fieldToAverageBy) });

まあ、私はそれができると思いました。ただし、これを列挙すると、NHibernate は次のように反応します。

Object of type 'System.Linq.Expressions.ConstantExpression' cannot be converted to type 'System.Linq.Expressions.LambdaExpression'.

したがって、舞台裏で、最初のケースでは私のラムダを受け入れるが、2番目のケースではNHibernateがSQLに変換できないものにする何らかの変換またはキャストまたはそのようなことが起こっていると推測しています。

私の質問は単純であるといいのですが、NHibernate 3.0 LINQ サポート (.Query() メソッド) がこれを SQL に変換するときに、GetAverageField 関数が Average() のパラメーターとして機能するものを返すにはどうすればよいですか?

どんな提案も歓迎します、ありがとう!

編集

彼の回答での David B からのコメントに基づいて、私はこれを詳しく調べました。Func が正しい戻り値の型であるという私の仮定は、Average() メソッドで取得したインテリセンスに基づいていました。Queryable 型ではなく、Enumerable 型に基づいているようです。それは奇妙です..物事をもう少し詳しく見る必要があります。

GroupBy メソッドには、次のリターン シグネチャがあります。

IQueryable<IGrouping<string,TestGuy>>

つまり、IQueryable が返されるはずです。ただし、次の行に進みます。

.Select(g => new { City = g.Key, Avg = g.Average(tg => tg.InterestingValue) });

新しい { } オブジェクト定義内の g 変数のインテリセンスを確認すると、実際には IGrouping 型 (IQueryable> ではありません) としてリストされています。これが、呼び出された Average() メソッドが Enumerable であり、David B によって提案された Expression パラメーターを受け入れない理由です。

どういうわけか、私のグループの値は、どこかで IQueryable としてのステータスを失ったようです。

少し興味深いメモ:

Select を次のように変更できます。

.Select(g => new { City = g.Key, Avg = g.AsQueryable<TestGuy>().Average(fieldToAverageBy) });

そして今、それはコンパイルされます! 黒魔術!ただし、NHibernate はもはや私を愛しておらず、次の例外を与えるため、問題は解決しません。

Could not parse expression '[-1].AsQueryable()': This overload of the method 'System.Linq.Queryable.AsQueryable' is currently not supported, but you can register your own parser if needed.

困惑するのは、Average() メソッドにラムダ式を指定するとこれが機能することですが、同じ式を引数として表す簡単な方法が見つからないことです。私は明らかに何か間違ったことをしていますが、何が見えません...!?

私は途方に暮れています。助けて、ジョン・スキート、あなたが私の唯一の希望です!;)

4

2 に答える 2

2

ラムダ式内で「ローカル」メソッドを呼び出すことはできません。これが単純なネストされていない句である場合、比較的単純になります。これを変更するだけです。

private Func<TestGuy,int> GetAverageField(SomeEnum someCondition)

これに:

private Expression<Func<TestGuy,int>> GetAverageField(SomeEnum someCondition)

次に、呼び出しの結果を関連するクエリ メソッドに渡します。

var results = query.Select(GetAverageField(fieldToAverageBy));

ただし、この場合、Select句の式ツリー全体 (匿名型作成式、キーの抽出、および平均フィールド部分の抽出) を構築する必要があります。正直なところ、面白くないでしょう。特に、式ツリーを構築するまでには、宣言で匿名型を表現できないため、通常のクエリ式と同じように静的に型付けされることはありません。

.NET 4 を使用している場合は、動的型付け役立つ場合がありますが、もちろん、静的型付けがなくなるという代償を払うことになります。

1 つのオプション (恐ろしいかもしれませんが) は、匿名型射影式ツリーの一種の「テンプレート」を使用して (たとえば、常に単一のプロパティを使用して)、その式ツリーのコピーを作成して、正しい式を挿入することです。代わりは。繰り返しますが、それは楽しいことではありません。

Marc Gravell はこれについてもっと手助けできるかもしれません - それは可能なはずのことのように聞こえますが、現時点ではそれをエレガントに行う方法について途方に暮れています.

于 2011-03-22T17:40:37.357 に答える
0

え?Queryable.Averageへのパラメーターは ではありませんFunc<T, U>。これはExpression<Func<T, U>>

これを行う方法は次のとおりです。

private Expression<Func<TestGuy,int>> GetAverageExpr(SomeEnum someCondition)
{
switch(someCondition)
{
case SomeEnum.Interesting:
  return tg => tg.InterestingValue;
case SomeEnum.Other:
  return tg => tg.OtherValue;
}
throw new InvalidOperationException("Not in my example!");
} 

に続く:

Expression<Func<TestGuy, int>> averageExpr = GetAverageExpr(someCondition);
var byCity = testGuys
  .GroupBy(c => c.City)
  .Select(g => new { City = g.Key, Avg = g.Average(averageExpr) });
于 2011-03-07T21:07:14.213 に答える