質問

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

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

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

データコンテキスト内で呼び出しフォームを作成すると、「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文字列を一度だけ生成し、後続のリクエストでパラメータ化されたコマンドとして使用する方法を見つける必要があるだけです。これは、データコンテキストでGetCommandメソッドを使用して手動で行うことができますが、その後、すべてのパラメータを明示的に設定する必要がありますそして、オブジェクト マッピングを自分で行います。この特定のクエリの複雑さを考慮すると、これは数百行のコードになります。

更新しました

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

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

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

この方法はすべてのケースに適しているわけではありませんが、同じ苦境に陥っている人には役立つはずです。

役に立ちましたか?

解決

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

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 メソッドを使用していることです。基本的に、合成を通じてクエリを構築し、完成した結果をコンパイルできます。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top