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

LINQ Expression Tree

You have learned about expressions in the previous section. Now, let's understand expression trees here.

As the name suggests, an expression tree is simply an expression arranged in a tree-like data structure. Each node in the expression tree is an expression. For example, an expression tree can be used to represent the mathematical formula x < y, where x, <, and y will be represented as expressions and arranged in a tree-like structure.

The expression tree is the memory representation form of lambda expressions. It saves the actual elements of the query, not the result of the query.

The expression tree makes the structure of lambda expressions transparent and explicit. You can interact with the data in the expression tree just like with any other data structure.

For example, let's look at the isTeenAgerExpr expression below:

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

The compiler will convert the above expression into the following expression tree:

Example: Expression Trees in 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 });

You can also manually construct an expression tree. Let's see how to construct an expression tree for the following simple lambda expression:

Example: Func delegate in C#:

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

This Func type delegate will be considered as the following method:

 C#:

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

To create an expression tree, first, create a parameter expression where Student is the type of the parameter, and 's' is the name of the parameter, as follows:

Steps1Creating Parameter Expressions in C#

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

Now, use Expression.Property() to create the s.Age expression, where s is the parameter and Age is the property name of Student. (Expressionis an abstract class that contains static helper methods for manually creating expression trees.)

Steps2Creating Property Expressions in C#

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

Now, for18Create a constant expression:

Steps3Creating Constant Expressions in C#

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

So far, we have s.Age (member expression) and18(constant expression) Constructs an expression tree. Now, we need to check if the member expression is greater than the constant expression. To do this, use the Expression.GreaterThanOrEqual() method and pass the member expression and constant expression as parameters::

Steps4Creating Binary Expressions in C#

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

Therefore, we use the lambda expression body s.Age> = 18 Constructs an expression tree. Now we need to connect the parameter expression and the main expression. Use Expression.Lambda(body, parameters array) Connect the lambda expression s => s.age > = 18body (main body) and parameter parts:

Steps5Creating Lambda Expressions in C#

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

In this way, you can build an expression tree for a simple Func delegate with a lambda expression.

Example: Expression Trees in 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("Expression tree: {0}", ExpressionTree);
        
Console.WriteLine("Expression tree body: {0}", ExpressionTree.Body);
        
Console.WriteLine("Body of the expression tree:  {0}", ExpressionTree.Body) 
                                ExpressionTree.Parameters.Count);
        
Console.WriteLine("Expression tree parameters: {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("Expression tree:  {0}", ExpressionTree)
        
Console.WriteLine("Body of the expression tree:  {0}", ExpressionTree.Body) 
                                Console.WriteLine("Number of parameters in the expression tree:  {0}",
        
Console.WriteLine("Parameters in the expression tree:  {0}", ExpressionTree.Parameters(0))
Output:}}
Expression tree:  s => (s.Age >= 18)
Body of the expression tree:  (s.Age >= 18)
Number of parameters in the expression tree: 1
Parameters in the expression tree:  s

The following figure illustrates the entire process of creating an expression tree:

Constructing an expression tree

Why choose expression trees?

In the previous section, we have seen that the code assigned to the lambda expressionFunc<T>compiled into executable code and assigned to lambda expressionsExpression<TDelegate>types compiled into Expression trees.

Executable code runs within the same application domain to handle in-memory collections. The static class of enumerables contains implementations forIEnumerable<T>extension methods for in-memory collections of interfaces, such as List<T>, Dictionary<T>, and so on. The extension methods in the Enumerable class acceptFuncpredicate parameter of type delegate. For example,WhereThe extension method acceptsFunc<TSource, bool> predicate. Then, it is compiled into IL (Intermediate Language) to handle memory collections within the same AppDomain.

The following figure shows the scenario where the Where extension method in the Enumerable class includes Func delegation as a parameter:

Where's Func delegation

FuncDelegation is the original executable code, so if you debug the code, you will findFuncDelegation is represented as opaque code. You cannot see its parameters, return type, and body:

Func delegate in debug mode

FuncDelegates are used for in-memory collections because they will be processed in the same AppDomain, but things like LINQ-to-What about remote LINQ query providers for SQL, EntityFramework, or third-party products that provide LINQ functionality? How will they parse lambda expressions that have been compiled into raw executable code to understand parameters, the return type of the lambda expression, and build runtime queries to further process? The answer isExpression Tree.

Expression<TDelegate> is compiled into a data structure called expression trees.

If you debug the code, the expression represents it as follows:

Expression Trees in Debug Mode

Now you can see the difference between normal delegates and expressions. Expression trees are transparent. You can retrieve parameter, return type, and body expression information from the expression as follows:

Expression<Func<Student, bool>> isTeenAgerExpr = s => s.Age > 12 && s.Age < 20;
Console.WriteLine("Expression: {0}", isTeenAgerExpr);
        
Console.WriteLine("Expression type: {0}", isTeenAgerExpr.NodeType);
var parameters = isTeenAgerExpr.Parameters;
foreach (var param in parameters)
{
    Console.WriteLine("Parameter name: {0}", param.Name);
    Console.WriteLine("Parameter type: {0}", param.Type.Name);
}
var bodyExpr = isTeenAgerExpr.Body as BinaryExpression;
Console.WriteLine("Expression body left: {0}", bodyExpr.Left);
Console.WriteLine("Binary expression type: {0}", bodyExpr.NodeType);
Console.WriteLine("Expression body right: {0}", bodyExpr.Right);
Console.WriteLine("Return type: {0}", isTeenAgerExpr.ReturnType);
Output:}}
Expression: s => ((s.Age > 12) AndAlso (s.Age < 20))
Expression type: Lambda
Parameter name: s
Parameter type: Student
Left side of the expression body: (s.Age > 12)
Binary expression type: AndAlso
Right side of the expression body: (s.Age < 20)
Return Type: System.Boolean

Not executed in the same application domain for LINQ-to-LINQ query for SQL or Entity Framework. For example, the following LINQ query for Entity Framework will never be executed internally in the program:

Example: LINQ Query in C#
var query = from s in dbContext.Students
            where s.Age >= 18
            select s;

First, convert it to a SQL statement and then execute it on the database server.

The code found in the query expression must be converted to a SQL query that can be sent as a string to another process. For LINQ-to-SQL or Entity Framework, the process is exactly the SQL Server database. Converting a data structure (such as an expression tree) to SQL is much easier than converting the original IL or executable code to SQL, because as you can see, it is easy to retrieve information from expressions.

The purpose of creating an expression tree is to convert code such as query expressions into strings that can be passed to other processes and executed here.

The queryable static classes include extension methods that accept a predicate parameter of type Expression. The predicate expression is converted to an expression tree and then passed as a data structure to the remote LINQ provider, so that the provider can build the appropriate query from the expression tree and execute the query.