方法: 式ツリーを使用して動的クエリをビルドする (C# および Visual Basic)

LINQ では、IQueryable<T> を実装するデータ ソースを対象とした構造化クエリを表すために式ツリーが使用されます。 たとえば、LINQ to SQL プロバイダーは、リレーショナル データ ストアのクエリを実行するための IQueryable<T> インターフェイスを実装します。 C# および Visual Basic コンパイラは、そのようなデータ ソースを対象とするクエリを、実行時に式ツリーをビルドするコードにコンパイルします。 その後、クエリ プロバイダーは式ツリーのデータ構造体を走査し、データ ソースに適したクエリ言語に変換します。

式ツリーは、Expression<TDelegate> 型の変数に代入されるラムダ式を表すためにも LINQ で使用されます。

このトピックでは、式ツリーを使用して動的 LINQ クエリを作成する方法について説明します。 動的クエリは、コンパイル時にクエリの詳細がわからない場合に便利です。 たとえば、あるアプリケーションに、エンド ユーザーがデータをフィルター処理するために 1 つ以上の述語を指定できるようなユーザー インターフェイスが用意されているとします。 LINQ を使用してクエリを行うために、この種のアプリケーションでは、実行時に式ツリーを使用して LINQ クエリを作成する必要があります。

使用例

式ツリーを使用して IQueryable データ ソースに対するクエリを作成し、その後それを実行する方法を次の例に示します。 コードは、次のクエリを表す式ツリーをビルドします。

C# クエリ

companies.Where(company => (company.ToLower() == "coho winery" || company.Length > 16)).OrderBy(company => company)

Visual Basic クエリ

companies.Where(Function(company) company.ToLower() = "coho winery" OrElse company.Length > 16).OrderBy(Function(company) company)

クエリ全体を構成する式を表す式ツリーを作成するために、System.Linq.Expressions 名前空間のファクトリ メソッドが使用されます。 標準クエリ演算子のメソッドの呼び出しを表す式は、そのメソッドの Queryable 実装を参照します。 最終的な式ツリーが IQueryable データ ソースのプロバイダーの CreateQuery<TElement>(Expression) 実装に渡され、IQueryable 型の実行可能クエリが作成されます。 結果を取得するには、そのクエリ変数を列挙します。

        Dim companies = 
            {"Consolidated Messenger", "Alpine Ski House", "Southridge Video", "City Power & Light", 
             "Coho Winery", "Wide World Importers", "Graphic Design Institute", "Adventure Works", 
             "Humongous Insurance", "Woodgrove Bank", "Margie's Travel", "Northwind Traders", 
             "Blue Yonder Airlines", "Trey Research", "The Phone Company", 
             "Wingtip Toys", "Lucerne Publishing", "Fourth Coffee"}

        ' The IQueryable data to query.
        Dim queryableData As IQueryable(Of String) = companies.AsQueryable()

        ' Compose the expression tree that represents the parameter to the predicate.
        Dim pe As ParameterExpression = Expression.Parameter(GetType(String), "company")

        ' ***** Where(Function(company) company.ToLower() = "coho winery" OrElse company.Length > 16) *****
        ' Create an expression tree that represents the expression: company.ToLower() = "coho winery".
        Dim left As Expression = Expression.Call(pe, GetType(String).GetMethod("ToLower", System.Type.EmptyTypes))
        Dim right As Expression = Expression.Constant("coho winery")
        Dim e1 As Expression = Expression.Equal(left, right)

        ' Create an expression tree that represents the expression: company.Length > 16.
        left = Expression.Property(pe, GetType(String).GetProperty("Length"))
        right = Expression.Constant(16, GetType(Integer))
        Dim e2 As Expression = Expression.GreaterThan(left, right)

        ' Combine the expressions to create an expression tree that represents the
        ' expression: company.ToLower() = "coho winery" OrElse company.Length > 16).
        Dim predicateBody As Expression = Expression.OrElse(e1, e2)

        ' Create an expression tree that represents the expression:
        ' queryableData.Where(Function(company) company.ToLower() = "coho winery" OrElse company.Length > 16)
        Dim whereCallExpression As MethodCallExpression = Expression.Call( 
                GetType(Queryable), 
                "Where", 
                New Type() {queryableData.ElementType}, 
                queryableData.Expression, 
                Expression.Lambda(Of Func(Of String, Boolean))(predicateBody, New ParameterExpression() {pe}))
        ' ***** End Where *****

        ' ***** OrderBy(Function(company) company) *****
        ' Create an expression tree that represents the expression:
        ' whereCallExpression.OrderBy(Function(company) company)
        Dim orderByCallExpression As MethodCallExpression = Expression.Call( 
                GetType(Queryable), 
                "OrderBy", 
                New Type() {queryableData.ElementType, queryableData.ElementType}, 
                whereCallExpression, 
                Expression.Lambda(Of Func(Of String, String))(pe, New ParameterExpression() {pe}))
        ' ***** End OrderBy *****

        ' Create an executable query from the expression tree.
        Dim results As IQueryable(Of String) = queryableData.Provider.CreateQuery(Of String)(orderByCallExpression)

        ' Enumerate the results.
        For Each company As String In results
            Console.WriteLine(company)
        Next

        ' This code produces the following output:
        '
        ' Blue Yonder Airlines
        ' City Power & Light
        ' Coho Winery
        ' Consolidated Messenger
        ' Graphic Design Institute
        ' Humongous Insurance
        ' Lucerne Publishing
        ' Northwind Traders
        ' The Phone Company
        ' Wide World Importers

            string[] companies = { "Consolidated Messenger", "Alpine Ski House", "Southridge Video", "City Power & Light",
                               "Coho Winery", "Wide World Importers", "Graphic Design Institute", "Adventure Works",
                               "Humongous Insurance", "Woodgrove Bank", "Margie's Travel", "Northwind Traders",
                               "Blue Yonder Airlines", "Trey Research", "The Phone Company",
                               "Wingtip Toys", "Lucerne Publishing", "Fourth Coffee" };

            // The IQueryable data to query.
            IQueryable<String> queryableData = companies.AsQueryable<string>();

            // Compose the expression tree that represents the parameter to the predicate.
            ParameterExpression pe = Expression.Parameter(typeof(string), "company");

            // ***** Where(company => (company.ToLower() == "coho winery" || company.Length > 16)) *****
            // Create an expression tree that represents the expression 'company.ToLower() == "coho winery"'.
            Expression left = Expression.Call(pe, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
            Expression right = Expression.Constant("coho winery");
            Expression e1 = Expression.Equal(left, right);

            // Create an expression tree that represents the expression 'company.Length > 16'.
            left = Expression.Property(pe, typeof(string).GetProperty("Length"));
            right = Expression.Constant(16, typeof(int));
            Expression e2 = Expression.GreaterThan(left, right);

            // Combine the expression trees to create an expression tree that represents the
            // expression '(company.ToLower() == "coho winery" || company.Length > 16)'.
            Expression predicateBody = Expression.OrElse(e1, e2);

            // Create an expression tree that represents the expression
            // 'queryableData.Where(company => (company.ToLower() == "coho winery" || company.Length > 16))'
            MethodCallExpression whereCallExpression = Expression.Call(
                typeof(Queryable),
                "Where",
                new Type[] { queryableData.ElementType },
                queryableData.Expression,
                Expression.Lambda<Func<string, bool>>(predicateBody, new ParameterExpression[] { pe }));
            // ***** End Where *****

            // ***** OrderBy(company => company) *****
            // Create an expression tree that represents the expression
            // 'whereCallExpression.OrderBy(company => company)'
            MethodCallExpression orderByCallExpression = Expression.Call(
                typeof(Queryable),
                "OrderBy",
                new Type[] { queryableData.ElementType, queryableData.ElementType },
                whereCallExpression,
                Expression.Lambda<Func<string, string>>(pe, new ParameterExpression[] { pe }));
            // ***** End OrderBy *****

            // Create an executable query from the expression tree.
            IQueryable<string> results = queryableData.Provider.CreateQuery<string>(orderByCallExpression);

            // Enumerate the results.
            foreach (string company in results)
                Console.WriteLine(company);

            /*  This code produces the following output:

                Blue Yonder Airlines
                City Power & Light
                Coho Winery
                Consolidated Messenger
                Graphic Design Institute
                Humongous Insurance
                Lucerne Publishing
                Northwind Traders
                The Phone Company
                Wide World Importers
            */

このコードでは、Queryable.Where メソッドに渡す述語に一定数の式を使用します。 しかし、ユーザー入力によって数が増減する述語式を結合するアプリケーションを作成することもできます。 また、ユーザー入力に応じて、クエリで呼び出される標準クエリ演算子を変更することもできます。

コードのコンパイル

  • Visual Studio で新しいコンソール アプリケーション プロジェクトを作成します。

  • System.Core.dll がまだ参照されていない場合は、System.Core.dll への参照を追加します。

  • System.Linq.Expressions 名前空間を含めます。

  • 例にあるコードをコピーし、Main メソッド (C#) または Main Sub プロシージャ (Visual Basic) に貼り付けます。

参照

処理手順

方法: 式ツリーを実行する (C# および Visual Basic)

方法 : 実行時に述語フィルターを動的に指定する (C# プログラミング ガイド)

概念

式ツリー (C# および Visual Basic)

その他の技術情報

LINQ Farm Seed: Using the Expression Tree Visualizer (LINQ ファーム シード: 式ツリー ビジュアライザーの使用)