5

式を受け取り、それを SQL データベースに対して分析するカスタム IQueryProvider クラスを作成しました (Linq2Sql を使用できることはわかっていますが、残念ながら Linq2Sql が不適切になるいくつかの変更と微調整が必​​要です)。クラスは、(属性を使用して) マークされているプロパティを識別して何かを実行しますが、式を LinqToObject プロバイダーに渡して、後で結果をフィルター処理できるようにしたいと考えています。

たとえば、次の linq 式があるとします。

var parents=Context.Parents
    .Where(parent=>parent.Name.Contains("T") && parent.Age>18);

Parents クラスは、IQueryProvider および IQueryable インターフェイスを実装するカスタム クラスですが、Age プロパティのみ取得用にマークされているため、Age プロパティは処理されますが、Name プロパティはマークされていないため無視されます。Age プロパティの処理が完了したら、式全体を LinqToObjects に渡して処理とフィルター処理を行いたいのですが、方法がわかりません。

注意: 式の Age 句を削除する必要はありません。これは、処理した後でも結果が同じになるため、常に式全体を LinqToObjects に送信できるためです。

次のコードを試しましたが、うまくいかないようです。

IEnumerator IEnumerable.GetEnumerator() {       
    if(this.expression != null && !this.isEnumerating) {
        this.isEnumerating = true;
        var queryable=this.ToList().AsQueryable();
        var query = queryable.Provider.CreateQuery(this.expression);
        return query.GetEnumerator();
    }
    return this;
}

this.isEnumrating は、再帰を防ぐために設定された単なるブール フラグです。

this.expression には次が含まれます。

{value(namespace.Parents`1[namespace.Child]).Where(parent => ((parent.Name.EndsWith("T") AndAlso parent.Name.StartsWith("M")) AndAlso (parent.Test > 0)))}

コードをステップ実行すると、結果がリストに変換されますが、それでもクエリにカスタム クラスが使用されます。クラス Parent が式の先頭にあったため、クエリをプロバイダーにルーティングしていたため、 this.expression をメソッド呼び出しの Argument[1] に設定しようとしたため、次のようになりました。

{parent => ((parent.Name.EndsWith("T") AndAlso parent.Name.StartsWith("M")) AndAlso (parent.Test > 0))}

私にはどちらが似ているように見えますが、これを CreateQuery 関数に渡すたびに、「引数式が無効です」というエラーが表示されます。

ただし、式のノード タイプは「Call」ではなく「Quote」になり、メソッドは null です。この式を呼び出し式にするだけでうまくいくと思いますが、どうすればよいかわかりません。

この式は where 句ですが、任意の種類の式である可能性があることに注意してください。リスト クエリ プロバイダーに渡す前に、式を分析して型を確認することは避けたいと思います。

おそらく、元の式の Parent クラスを除去またはリスト プロバイダー クラスに置き換える方法がありますが、式の種類に関係なく、式としてリスト プロバイダーに渡すことができる状態のままにしておく方法はありますか?

これに関する助けがあれば大歓迎です!

4

1 に答える 1

4

あなたはとても近かった!

私の目標は、SQL からオブジェクトへの式の機能セットを完全に「複製」する必要がないようにすることでした。そして、あなたは私を正しい軌道に乗せました (ありがとう!) カスタム IQueryable で SQL-to-Object をピギーバックする方法は次のとおりです。

public IEnumerator<T> GetEnumerator() {

    // For my case (a custom object-oriented database engine) I still 
    // have an IQueryProvider which builds a "subset" of objects each populated 
    // with only "required" fields, as extracted from the expression. IDs, 
    // dates, particular strings, what have you. This is "cheap" because it 
    // has an indexing system as well.

    var en = ((IEnumerable<T>)this.provider.Execute(this.expression));

    // Copy your internal objects into a list.

    var ar = new List<T>(en);
    var queryable = ar.AsQueryable<T>();

    // This is where we went wrong:
    // queryable.Provider.CreateQuery(this.expression);
    // We can't re-reference the original expression because it will loop 
    // right back on our custom IQueryable<>. Instead, swap out the first 
    // argument with the List's queryable:

    var mc = (MethodCallExpression)this.expression;
    var exp = Expression.Call(mc.Method, 
                    Expression.Constant(queryable), 
                    mc.Arguments[1]);


    // Now the CLR can do all of the heavy lifting
    var query = queryable.Provider.CreateQuery<T>(exp);
    return query.GetEnumerator();
}

LINQ-to-Object クエリで車輪の再発明を回避する方法を理解するのに 3 日もかかったなんて信じられません。

于 2013-07-23T18:51:25.203 に答える