6

CompiledQuery.Compile メソッドを使用して、IQueryable に関連付けられた Expression をコンパイルする方法はありますか? 現在、背後に非常に大きな式ツリーを持つ IQueryable があります。IQueryable は、それぞれがコンポーネントを提供するいくつかのメソッドを使用して構築されました。たとえば、2 つのメソッドが IQueryables を返し、それらが 3 つ目のメソッドに結合される場合があります。このため、compile() メソッド呼び出し内で式全体を明示的に定義することはできません。

式を someIQueryable.Expression としてコンパイル メソッドに渡したいと思っていましたが、この式はコンパイル メソッドで必要な形式ではありません。クエリをコンパイルメソッドに直接入れることでこれを回避しようとすると、次のようになります。

    var foo = CompiledQuery.Compile<DataContext, IQueryable<User>>(dc => dc.getUsers());
    var bar = foo(this);

datacontext 内で呼び出しフォームを作成すると、「getUsers がストアド プロシージャまたはユーザー定義関数としてマップされていません」というエラーが表示されます。ここでも、getUsers メソッドの内容をコンパイル呼び出しを行う場所にコピーすることはできません。これは、他のメソッドを使用するためです。

「getUsers」から返された IQueryable の Expression を Compile メソッドに渡す方法はありますか?

更新 しました 次のコードを使用して、システムに自分の意志を強制しようとしました:

    var phony = Expression.Lambda<Func<DataContext, IQueryable<User>>>(
        getUsers().Expression, Expression.Parameter(typeof(DataContext), "dc"));

    Func<DataContext, IQueryable<User>> wishful = CompiledQuery.Compile<DataContext, IQueryable<User>>(phony);
    var foo = wishful(this);

foo は次のようになります。

{System.Data.Linq.SqlClient.SqlProvider+OneTimeEnumerable`1[Model.Entities.User]}

結果を展開してクエリを実行する代わりに、「操作によりランタイムが不安定になる可能性があります」というメッセージしか表示されないため、foo で結果を表示するオプションはありません。

SQL 文字列を 1 回だけ生成し、後続のリクエストでパラメータ化されたコマンドとして使用する方法を見つける必要があるだけです。これは、データ コンテキストで GetCommand メソッドを使用して手動で行うことができますが、すべてのパラメータを明示的に設定する必要があります。この特定のクエリの複雑さを考えると、これは数百行のコードです。

更新しました

ジョン・ラスクが最も有益な情報を提供してくれたので、私は彼にこの件での勝利を与えました。ただし、いくつかの追加の調整が必要であり、途中で遭遇した他のいくつかの問題があったため、答えを「拡張」したいと思いました. まず、「操作によりランタイムが不安定になる可能性があります」というエラーは、式のコンパイルが原因ではなく、実際には式ツリーの奥深くにあるキャストが原因でした。.Cast<T>()一部の場所では、アイテムが正しいタイプであっても、アイテムを正式にキャストするためにメソッドを呼び出す必要がありました。詳細は省きますが、これは基本的に、いくつかの式が 1 つのツリーに結合され、各分岐が異なる型 (それぞれが共通クラスのサブ型) を返す可能性がある場合に必要でした。

不安定化の問題を解決した後、コンパイルの問題に戻りました。ジョンの拡張ソリューションはほぼ完成しました。ツリーでメソッド呼び出し式を探し、メソッドが通常返す基になる式に解決しようとしました。式への参照は、メソッド呼び出しではなく、プロパティによって提供されました。したがって、展開を実行する式ビジターを変更して、これらの型を含める必要がありました。

protected override Expression VisitMemberAccess(MemberExpression m) {
    if(m.Method.DeclaringType == typeof(ExpressionExtensions)) {
        return new ExpressionExpander().Visit((Expression)(((System.Reflection.PropertyInfo)m.Member).GetValue(null, null)));
    }
    return base.VisitMemberAccess(m);
}

この方法はすべての場合に適切であるとは限りませんが、同じ苦境に陥った人には役立つはずです。

4

1 に答える 1

2

少なくとも私のテストでは、このようなものが機能します。

Expression<Func<DataContext, IQueryable<User>> queryableExpression = GetUsers();
var expressionWithSomeAddedStuff = (DataContext dc) => from u in queryableExpression.Invoke(dc) where ....;
var expressionThatCanBeCompiled = expressionWithSomeAddedStuff.Expand();
var foo = CompiledQuery.Compile<DataContext, IQueryable<User>>(expressionThatCanBeCompiled);

これは少し冗長に見えます、そしておそらくあなたがすることができる改善があるでしょう。

重要な点は、LinqKitのInvokeメソッドとExpandメソッドを使用していることです。基本的に、コンポジションを介してクエリを作成し、完成した結果をコンパイルすることができます。

于 2009-05-05T02:55:20.627 に答える