English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Дерево выражений LINQ

Вы уже узнали о выражениях в предыдущем разделе. Теперь давайте рассмотрим деревья выражений здесь.

Как следует из названия, деревья выражений - это выражения, организованные в виде древовидной структуры данных. Каждый узел в дереве выражений - это выражение. Например, дерево выражений может использоваться для представления математической формулы x < y, где x, < и y будут представлены как выражения и организованы в древовидной структуре.

Деревья выражений - это память представления lambda-выражений. Они сохраняют фактические элементы запроса, а не результат запроса.

Деревья выражений делают структуру lambda-выражений прозрачной и явной. Вы можете взаимодействовать с данными в деревьях выражений, как с любыми другими данными.

Например, посмотрите на выражение isTeenAgerExpr:

Expression<Func<Student, bool>> isTeenAgerExpr = s => s.age > 12 && s.age < 20;

Компилятор преобразует выражение в следующий деревьев выражений:

Пример: древовидное выражение в C#

Expression.Lambda<Func<Student, bool>>(
                Expression.AndAlso(
                    Expression.GreaterThan(Expression.Property(pe, "Age"), Expression.Constant(12, typeof(int))),
                    Expression.LessThan(Expression.Property(pe, "Age"), Expression.Constant(20, typeof(int)))),
                        new[] { pe });

Вы также можете вручную построить дерево выражений. Давайте посмотрим, как построить дерево выражений для следующего простого выражения lambda:

Пример: делегат Func в C#:

Func<Student, bool> isAdult = s => s.age >= 18;

Этот тип делегата Func будет рассматриваться как следующий метод:

 C#:

public bool function(Student s)
{
  return s.Age > 18;
}

Чтобы создать дерево выражений, сначала создайте выражение параметра, где Student является типом параметра, а 's' является именем параметра, как показано ниже:

Шаг 1: Создание выражения параметра в C#

ParameterExpression pe = Expression.Parameter(typeof(Student), "s");

Теперь создайте выражение свойств s.Age с помощью Expression.Property(), где s является параметром, а Age является именем свойства Student.Expressionявляется абстрактным классом, который содержит статические помощники для ручного создания дерева выражений.)

Шаг 2: Создание выражения свойств в C#

MemberExpression me = Expression.Property(pe, "Age");

Теперь создайте константное выражение для 18:

Шаг 3: Создание константного выражения в C#

ConstantExpression constant = Expression.Constant(18, typeof(int));

До сих пор мы построили дерево выражений для члена выражения s.Age (выражение члена) и константы 18 (константное выражение). Теперь нам нужно проверить, больше ли член выражения, чем константное выражение. Для этого используйте метод Expression.GreaterThanOrEqual() и передайте член выражения и константное выражение в качестве параметров:

Шаг 4: Создание двоичного выражения в C#

BinaryExpression body = Expression.GreaterThanOrEqual(me, constant);

Таким образом, мы построили дерево выражений для тела выражения lambda s.Age >= 18. Теперь нам нужно соединить выражение параметра и выражение тела. Используйте Expression.Lambda(body, array параметров) свяжите часть body (тело) и часть parameter (параметр) выражения lambda s => s.age >= 18:

Шаг 5: Создание выражения Lambda в C#

var isAdultExprTree = Expression.Lambda<Func<Student, bool>>(body, new[] { pe });

Таким образом, вы можете создать дерево выражений для простого Func делегата с lambda-выражением.

Пример: древовидное выражение в C#

ParameterExpression pe = Expression.Parameter(typeof(Student), "s");
MemberExpression me = Expression.Property(pe, "Age");
ConstantExpression constant = Expression.Constant(18, typeof(int));
BinaryExpression body = Expression.GreaterThanOrEqual(me, constant);
var ExpressionTree = Expression.Lambda<Func<Student, bool>>(body, new[] { pe });
Console.WriteLine("Выражение дерева: {0}", ExpressionTree);
        
Console.WriteLine("Тело выражения дерева: {0}", ExpressionTree.Body);
        
Console.WriteLine("Количество параметров в дереве выражений: {0}", 
                                ExpressionTree.Parameters.Count);
        
Console.WriteLine("Параметры выражения дерева: {0}", ExpressionTree.Parameters[0]);
Dim pe As ParameterExpression = Expression.Parameter(GetType(Student), "s")
Dim mexp As MemberExpression = Expression.Property(pe, "Age")
Dim constant As ConstantExpression = Expression.Constant(18, GetType(Integer))
Dim body As BinaryExpression = Expression.GreaterThanOrEqual(mexp, constant)
Dim ExpressionTree As Expression(Of Func(Of Student, Boolean)) = 
    Expression.Lambda(Of Func(Of Student, Boolean))(body, New ParameterExpression() { pe })
Console.WriteLine("Дерево выражений: {0}", ExpressionTree)
Console.WriteLine("Тело дерева выражений: {0}", ExpressionTree.Body)
        
Console.WriteLine("Количество параметров в дереве выражений: {0}", 
                                ExpressionTree.Parameters.Count)
        
Console.WriteLine("Параметр дерева выражений: {0}", ExpressionTree.Parameters(0))
Вывод:
Дерево выражений: s => (s.Age >= 18)
Тело дерева выражений: (s.Age >= 18)
Количество параметров в дереве выражений: 1
Параметр дерева выражений: s

Следующий рисунок поясняет整个过程 создания дерева выражений:

Конструирование дерева выражений

Почему выбирают дерево выражений?

В предыдущем разделе мы уже видели, что назначается лямбда-выражениюFunc<T>компилируется в исполнимый код и назначается лямбда-выражениюExpression<TDelegate>типов, компилируемых в дерево выражений.

Исполняемый код выполняется в той же приложенческой домене для обработки集合 в памяти. Статический класс enumerable содержит методы для реализацииIEnumerable <T>расширяемые методы памяти для интерфейсов, таких как List <T>, Dictionary <T> и т.д. Расширяемые методы класса Enumerable принимаютFuncпараметр предиката типа делегата. Например,WhereРасширяемый метод принимаетПредикат Func <TSource, bool>. Затем его компилируют в IL (промежуточный язык) для обработки集合 в той же AppDomain.

На следующем рисунке показано, как расширяемый метод Where класса Enumerable включает делегат Func в качестве параметра:

Делегат Func

FuncДелегат является исходным исполнимым кодом, поэтому, если отладить код, то можно обнаружить, что:FuncДелегат будет представлен в виде неопределенного кода. Вы не можете видеть его параметры, тип возвращаемого значения и тело:

Делегат Func в режиме отладки

FuncДелегаты используются для集合ов в памяти, так как они будут обрабатываться в одном AppDomain, но что делать с удаленными LINQ-запросами, такими как LINQ-to-SQL, EntityFramework или другими третьими сторонними продуктами, предоставляющими функции LINQ? Как они будут анализировать лямбда-выражения, скомпилированные в исходный выполняемый код, чтобы понять параметры, тип возвращаемого значения лямбда-выражения и построить runtime-запрос для дальнейшей обработки? Ответ:Деревовидное выражение.

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

Если вы отладываете код, выражение будет представлено следующим образом:

Деревовидное выражение в режиме отладки

Теперь вы можете видеть различие между обычными делегатами и выражениями. Деревовидное выражение прозрачно. Вы можете извлечь из выражения параметры, тип возвращаемого значения и информацию о теле выражения, как показано ниже:

Expression<Func<Student, bool>> isTeenAgerExpr = s => s.Age > 12 && s.Age < 20;
Console.WriteLine("Expression: {0}", isTeenAgerExpr);
        
Console.WriteLine("Тип выражения: {0}", isTeenAgerExpr.NodeType);
var parameters = isTeenAgerExpr.Parameters;
foreach (var param in parameters)
{
    Console.WriteLine("Имя параметра: {0}", param.Name);
    Console.WriteLine("Тип параметра: {0}", param.Type.Name);
}
var bodyExpr = isTeenAgerExpr.Body as BinaryExpression;
Console.WriteLine("Левый край выражения: {0}", bodyExpr.Left);
Console.WriteLine("Тип бинарного выражения: {0}", bodyExpr.NodeType);
Console.WriteLine("Правый край выражения: {0}", bodyExpr.Right);
Console.WriteLine("Возврат типа: {0}", isTeenAgerExpr.ReturnType);
Вывод:
Выражение: s => ((s.Age > 12) AndAlso (s.Age < 20))
Тип выражения: Lambda
Имя параметра: s
Тип параметра: Student
Левая сторона выражения: (s.Age > 12)
Тип бинарного выражения: AndAlso
Правая сторона выражения: (s.Age < 20)
Возврат типа: System.Boolean

Запросы LINQ к LINQ-to-SQL или Entity Framework не выполняются в одной и той же области приложения. Например, следующий запрос LINQ к Entity Framework никогда не выполняется внутри программы:

Пример: запрос LINQ в C#
var query = from s in dbContext.Students
            where s.Age >= 18
            select s;

Сначала преобразуйте его в команду SQL, а затем выполните на сервере базы данных.

Код, найденный в выражениях запроса, необходимо преобразовать в запрос SQL, который можно отправить в другой процесс в виде строки. Для LINQ-to-SQL или Entity Framework этот процесс именно SQL Server база данных. Преобразование структуры данных (например, дерева выражений) в SQL значительно проще, чем преобразование исходного IL или исполняемого кода в SQL, потому что, как вы видите, извлечение информации из выражений легко.

Цель создания дерева выражений - преобразовать код, такой как запрос выражения, в строку, которую можно передать другому процессу и выполнить здесь.

Классы, подлежащие запросу, включают расширяемые методы, принимающие параметры предиката типа Expression. Преобразуйте выражение предиката в дерево выражений и передайте его в качестве структуры данных в удаленный поставщик LINQ, чтобы поставщик мог построить соответствующий запрос и выполнить его.