Практическое руководство. Использование деревьев выражений для построения динамических запросов (C# и Visual Basic)

Деревья выражений используются в LINQ для представления структурированных запросов к источникам данных, которые реализуют IQueryable<T>.Например, поставщик LINQ to SQL реализует интерфейс IQueryable<T> для запросов к реляционным хранилищам данных.Компиляторы C# и Visual Basic компилируют запросы к источникам данных в код, который строит дерево выражений во время выполнения.Поставщик запросов может пройти через структуру данных для дерева выражений и преобразовать ее в язык запросов, соответствующий для источника данных.

Деревья выражений также используются в LINQ для представления лямбда выражений, которые присваиваются переменным типа Expression<TDelegate>.

В этом разделе описывается использование деревьев выражений для создания динамических запросов LINQ.Динамические запросы целесообразны при наличии во время компиляции некоторых неизвестных особенностей запроса.Например, приложение может предоставлять пользовательский интерфейс, позволяющий конечному пользователю указать один или несколько предикатов для фильтрации данных.Чтобы использовать 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 этих методов.Итоговое дерево выражения передается в реализацию CreateQuery<TElement>(Expression) поставщика источника данных IQueryable для создания исполняемого запроса типа .Для получения результатов используется перечисление переменных запроса.

        ' Add an Imports statement for System.Linq.Expressions.

        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

            // Add a using directive for System.Linq.Expressions.

            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.Linq.Expressions.

  • Скопируйте код из примера и вставьте его в метод Main (C#) или в процедуру Main Sub (Visual Basic).

См. также

Задачи

Практическое руководство. Выполнение деревьев выражений (C# и Visual Basic)

Практическое руководство. Динамическое определение фильтров предикатов во время выполнения (Руководство по программированию на C#)

Основные понятия

Деревья выражений (C# и Visual Basic)

Другие ресурсы

Основы LINQ: Использование визуализатора дерева выражений