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

Árvore de expressão LINQ

Você já aprendeu sobre expressões no capítulo anterior. Agora, vamos entender a árvore de expressões aqui.

Como o nome sugere, a árvore de expressões nada mais é do que expressões organizadas em uma estrutura de dados em forma de árvore. Cada nó na árvore de expressões é uma expressão. Por exemplo, a árvore de expressões pode ser usada para representar a fórmula matemática x < y, onde x, < e y serão representados como expressões e organizados em uma estrutura em forma de árvore.

A árvore de expressões é a representação em memória da expressão lambda. Ela armazena os elementos reais da consulta, não o resultado da consulta.

A árvore de expressões torna a estrutura da expressão lambda transparente e explícita. Você pode interagir com os dados na árvore de expressões da mesma forma que com qualquer outra estrutura de dados.

Por exemplo, veja a expressão isTeenAgerExpr a seguir:

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

O compilador convertirá a expressão acima em uma árvore de expressões a seguir:

Exemplo: árvore de expressões em 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 });

Você também pode construir manualmente a árvore de expressão. Vamos ver como construir a árvore de expressão para a seguinte expressão lambda simples:

Exemplo: Delegado Func em C#:

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

Este delegado do tipo Func será considerado o seguinte método:

 C#:

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

Para criar uma árvore de expressão, primeiro, crie uma expressão de parâmetro, onde Student é o tipo do parâmetro e 's' é o nome do parâmetro, conforme mostrado a seguir:

Passos1Criar expressão de parâmetro em C#

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

Agora, use Expression.Property() para criar a expressão s.Age, onde s é o parâmetro e Age é o nome da propriedade Student. (Expressioné uma classe abstrata, que contém métodos estáticos de assistência para criar manualmente a árvore de expressão.)

Passos2Criar expressão de propriedade em C#

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

Agora, para18Crie uma expressão constante:

Passos3Criar expressão constante em C#

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

Até agora, já criamos para s.Age (expressão de membro) e18(expressão constante) Construiu uma árvore de expressão. Agora, precisamos verificar se a expressão de membro é maior que a expressão constante. Para isso, use o método Expression.GreaterThanOrEqual() e passe a expressão de membro e a expressão constante como parâmetros::

Passos4Criar expressão binária em C#

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

Portanto, criamos a expressão de corpo da expressão lambda s.Age> = 18 Construiu uma árvore de expressão. Agora, precisamos conectar a expressão de parâmetro e a expressão de corpo. Usando Expression.Lambda(corpo, array de parâmetros) conecta a expressão lambda s => s.age> = 18a parte do corpo (corpo) e parte do parâmetro (parâmetro):

Passos5Criar expressão Lambda em C#

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

Desta forma, você pode construir uma árvore de expressões para delegados Func simples com expressões lambda.

Exemplo: árvore de expressões em 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("Árvore de expressões: {0}", ExpressionTree);
        
Console.WriteLine("Corpo da árvore de expressões: {0}", ExpressionTree.Body);
        
Console.WriteLine("Corpo da árvore de expressão: {0}", ExpressionTree.Body) 
                                ExpressionTree.Parameters.Count);
        
Console.WriteLine("Parâmetros da árvore de expressões: {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)) = 
    Dim ExpressionTree As Expression(Of Func(Of Student, Boolean)) =
Expression.Lambda(Of Func(Of Student, Boolean))(body, New ParameterExpression() { pe })
Console.WriteLine("Árvore de expressão: {0}", ExpressionTree)
        
Console.WriteLine("Corpo da árvore de expressão: {0}", ExpressionTree.Body) 
                                Console.WriteLine("Número de parâmetros na árvore de expressão: {0}",
        
Console.WriteLine("Parâmetros na árvore de expressão: {0}", ExpressionTree.Parameters(0))
Saída:}}
Árvore de expressão: s => (s.Age >= 18)
Corpo da árvore de expressão: (s.Age >= 18)
Número de parâmetros na árvore de expressão: 1
Parâmetros na árvore de expressão: s

A figura a seguir ilustra todo o processo de criação de árvores de expressão:

Construir árvores de expressão

Por que escolher árvores de expressão?

No capítulo anterior, vimos que foi alocado para expressões lambdaFunc<T>compilado para código executável, alocado para expressões lambdaExpression<TDelegate>tipos compilados para árvores de expressão.

código executável é executado no mesmo domínio de aplicativo para lidar com conjuntos em memória. A classe estática enumerável contém métodos para implementarIEnumerable <T>métodos de extensão de conjuntos em memória de interface, por exemplo List <T>, Dictionary <T> e outros. Os métodos de extensão na classe Enumerable aceitamFuncparâmetro de predicado da delegação de tipo. Por exemplo:WhereO método de extensão aceitaPredicado Func <TSource, bool>Em seguida, compilá-lo para IL (linguagem intermediária) para lidar com conjuntos em memória no mesmo AppDomain.

A figura a seguir mostra o método de extensão Where da classe Enumerable, incluindo a delegação Func como parâmetro:

A delegação Func

FuncA delegação é código executável original, portanto, se você debugar o código, descobriráFuncA delegação será representada por código opaco. Você não pode ver seus parâmetros, tipo de retorno e corpo:

Delegação Func em modo de depuração

FuncDelegação é usada para conjuntos em memória, pois será processado no mesmo AppDomain, mas como LINQ-to-O que fazem os provedores de consulta remota LINQ para SQL, EntityFramework ou outros produtos de terceiros que oferecem LINQ? Eles como analisar expressões lambda compiladas em código executável original para entender os parâmetros, o tipo de retorno da expressão lambda e construir consultas de tempo de execução para processamento adicional? A resposta éExpressão.

Expression<TDelegate> é compilado em uma estrutura de dados chamada árvore de expressões.

Se o código for depurado, a expressão representará o seguinte:

Árvore de expressões em modo de depuração

Agora você pode ver a diferença entre delegados comuns e expressões. A árvore de expressões é transparente. Você pode recuperar informações de parâmetros, tipo de retorno e expressão principal da expressão, conforme mostrado a seguir:

Expression<Func<Student, bool>> isTeenAgerExpr = s => s.Age > 12 && s.Age < 20;
Console.WriteLine("Expressão: {0}", isTeenAgerExpr);
        
Console.WriteLine("Tipo de expressão: {0}", isTeenAgerExpr.NodeType);
var parameters = isTeenAgerExpr.Parameters;
foreach (var param in parameters)
{
    Console.WriteLine("Nome do parâmetro: {0}", param.Name);
    Console.WriteLine("Tipo do parâmetro: {0}", param.Type.Name);
}
var bodyExpr = isTeenAgerExpr.Body as BinaryExpression;
Console.WriteLine("Lado esquerdo da expressão: {0}", bodyExpr.Left);
Console.WriteLine("Tipo de expressão binária: {0}", bodyExpr.NodeType);
Console.WriteLine("Lado direito da expressão: {0}", bodyExpr.Right);
Console.WriteLine("Tipo de retorno: {0}", isTeenAgerExpr.ReturnType);
Saída:}}
Expressão: s => ((s.Age > 12) AndAlso (s.Age < 20))
Tipo de expressão: Lambda
Nome do parâmetro: s
Tipo de parâmetro: Student
Lado esquerdo do corpo da expressão: (s.Age > 12)
Tipo de expressão binária: AndAlso
Lado direito do corpo da expressão: (s.Age < 20)
Tipo de retorno: System.Boolean

Não execute consultas LINQ em domínios de aplicativo diferentes-to-Consulta LINQ no SQL ou Entity Framework. Por exemplo, a seguinte consulta LINQ do Entity Framework nunca será executada internamente no programa:

Exemplo: consulta LINQ no C#
var query = from s in dbContext.Students
            where s.Age >= 18
            select s;

Primeiro converta-a em uma sentença SQL e, em seguida, execute na máquina do servidor de banco de dados.

O código encontrado na expressão de consulta deve ser convertido em uma consulta SQL, que pode ser enviada como uma string para outro processo. Para LINQ-to-SQL ou Entity Framework, o processo é exatamente o banco de dados SQL Server. Convertir uma estrutura de dados (como uma árvore de expressão) para SQL é muito mais fácil do que converter código IL ou código executável original para SQL, porque, como você vê, é fácil extrair informações da expressão.

O objetivo de criar uma árvore de expressão é converter código como expressões de consulta em strings que podem ser passadas para outro processo e executadas aqui.

Classes estáticas consultáveis incluem métodos de extensão que aceitam parâmetros de predicado do tipo Expression. Converta a expressão predicativa em uma árvore de expressão e, em seguida, passe essa árvore de expressão como uma estrutura de dados para o provedor LINQ remoto, para que o provedor possa construir a consulta apropriada a partir da árvore de expressão e executar a consulta.