Expression Trees in C#: Building Dynamic LINQ Queries at Runtime
Tired of endless if-else blocks just to build queries? What if your LINQ queries could write themselves at runtime? Today, we will unfold expression trees, which can be used to create dynamic queries at runtime. I will show how to use expression trees in your project and understand their advantages and limitations.

What is an expression tree
Expression trees are immutable data structures in C# that represent code in a tree-like data structure. An expression tree is composed of expressions such as a binary operation, a method call, or a constant value, each represented as a node. They enable you to build code and LINQ queries at runtime and execute them based on user input.
Use cases of expression trees
Expression trees offer a range of uses, making programming easy and robust. Let's go through some common use cases.
Dynamic LINQ providers: Entity Framework uses expression trees to parse LINQ queries behind the scenes to translate them into SQL statements. When you write a LINQ query like context.Buildings.Where(b => b.Name == "Tower A")
, the LINQ provider inspects the expression tree representing b => b.Name == "Tower A"
. After successfully verifying the expression tree, the LINQ query translates it into a SQL query (SELECT * FROM Buildings WHERE Name= "Tower A"
).
Code generation at runtime: An expression tree can be your dynamic code builder. You can define methods as expression tree lambdas, then compile them into delegates.
A Meta-Programming tool: As expression trees access the runtime, they can be a tool for meta-programming scenarios where you can inspect the structure of code. If you want to know more about meta-programming with reflection, make sure to read my other post 4 real-life examples of using reflection in C#.
Building Dynamic LINQ Queries: While LINQ queries are a luxury over writing SQL, you can go one step beyond with expression trees. With the help of expression predicates, you can generate dynamic LINQ queries for different conditions at runtime. This provides an escape from the need for complex code for multiple conditions and is useful when constructing search filters or complex queries based on dynamic user input.
Example: Runtime code for basic addition operation
Let's understand expression trees using an example. I will construct code to add a constant value to the user input and return the result.
using System.Linq.Expressions;
ParameterExpression param = Expression.Parameter(typeof(int), "x");
ConstantExpression constant = Expression.Constant(5);
BinaryExpression body = Expression.Add(param, constant);
Expression<Func<int, int>> lambda = Expression.Lambda<Func<int, int>>(body, param);
var compiledLambda = lambda.Compile();
int result = compiledLambda(10);
Console.WriteLine(result);
Output

As we know, each expression is a piece of code that produces a value.
Expression can be a constant or variable, or a complex method call with operations. Each step is defined as an expression. ParameterExpression
represents an input parameter at runtime, specifying the type as int
and the name as x
. I initialized ConstantExpression
for a hardcoded value that I will use later in the code. BinaryExpression
creates a binary operation node where I add parameters with a constant using Expression.Add
. The Expression<Func<int, int>>
in lambda = Expression.Lambda<Func<int, int>>(body, param)
creates a strongly typed lambda expression tree that matches the delegate Func<int, int>
. The Func
delegate specifies the input and output type by rule Func<TInput, TOutput>
. Finally, lambda.Compile()
converts the expression tree into executable IL code. We can reuse the compiled code with compiledLambda(10)
.
Example: Filtering buildings with dynamic fields
In this next example, we want to let users filter buildings at runtime by any property they choose. We have a building with the following properties:
public class Building
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public int Floors { get; set; }
public string City { get; set; } = string.Empty;
public int YearBuilt { get; set; }
}
Now, we need a dynamic LINQ query that allows users to filter by any property at runtime. Applying that is difficult and less maintainable with static LINQ. Here comes the expression tree to aid
var buildings = new List<Building>
{
new Building { Id = 1, Name = "Tower One", Floors = 10, City = "Karachi", YearBuilt = 1990 },
new Building { Id = 2, Name = "Skyline Plaza", Floors = 25, City = "Lahore", YearBuilt = 2005 },
new Building { Id = 3, Name = "Heritage Hall", Floors = 5, City = "Karachi", YearBuilt = 1950 }
};
var propertyName = "City"; // could be "Floors", "YearBuilt", etc.
object filterValue = "Karachi"; // could be 10, 2005, etc.
var parameter = Expression.Parameter(typeof(Building), "b");
var property = Expression.Property(parameter, propertyName);
var constant = Expression.Constant(filterValue);
var body = Expression.Equal(property, constant);
var lambda =
Expression.Lambda<Func<Building, bool>>(body, parameter);
// Use the dynamic lambda in LINQ
var result = buildings.AsQueryable().Where(lambda);
foreach (var building in result)
{
Console.WriteLine($"{building.Name} - {building.City}");
}
First, I created a list of buildings to keep things simple. Then I pretended input values as variables propertyName
and filterValue
. I defined a parameter as a type of Building
and named it b
. Then the Expression.Property
specifies the input property in the parameter that will be used in the filter. Next, filterValue
is defined as a constant. Actual operation is in the body that specifies a comparison between the input value and the selected property with Expression.Equal
. Finally, a lambda expression is defined matching a delegate with Building
as input and bool
as output. In the result part, the lambda is used as a filter inside the Where
method.
Output

Now changing the input property and filter value:
var propertyName = "Floors";
object filterValue = 25;
Output

Hence, we simply changed the propertyName
and filterValue
to 'Floors' and 25, respectively, just as the user input would have.
Benefits of expression trees
Custom Query Builders: You can use expression trees to build custom query builders that generate complex search queries based on dynamic conditions or business-specific scenarios.
Reduced code complexity: By using dynamic LINQ queries, you can avoid writing complex, hard-to-maintain code that is required to build dynamic queries using expression trees or other techniques.
Enhanced readability and maintainability: Dynamic LINQ queries often result in more readable code, as you can use familiar query syntax instead of dealing with the intricacies of expression trees.
Ease of use: The Dynamic LINQ library simplifies the process of building dynamic queries, making it easier for developers to implement runtime query generation.
Limitations of expression trees
Despite offering flexibility in queries and code, expression trees have some limitations.
Can't Represent Every C# Feature: expression trees do not support several code constructs, such as loops, goto statements, local variables (declared and reassigned inside the tree), tuple literals, and tuple comparisons, as well as try/catch/finally blocks. Moreover, you cannot use Interpolated Strings or UTF-8 string conversions.
Read-Only Structure: An immutable tree means once created, you can't "change" a node. You have to rebuild the tree if you want modifications.
Performance overhead: lambda.Compile()
incurs compilation overhead. Executing compiled delegates is fast, but still usually a bit slower than plain C# methods.
Entity Framework translation: Expression trees compiled with Compile()
cannot be translated into SQL by Entity Framework or other LINQ providers. They are evaluated only in memory. Use uncompiled expression trees directly with providers when you want server-side execution.
Tips to use expression trees to their fullest
Expression trees benefit your project in many ways. But you need to consider the best scenario to use.
Use where necessary: Expression trees are verbose and complex. Unlike plain C# code, they involve initialization of constants, parameters, properties, and compilation that can be tricky to maintain. If your query is static and known at compile time, use regular LINQ and avoid overengineering the code.
Cache compiled lambda: As mentioned earlier, compilation of the expression tree lambda is expensive. Cache the compiled delegate and reuse it:
var compiled = lambda.Compile();
_cache["MyLambda"] = compiled;
Break down into small reusable builders: Simplicity is the key to any code. Follow this rule of thumb in expression tree code. Break down a complex query into smaller, reusable methods:
public static Expression<Func<Building, bool>> FloorsGreaterThan(int minFloors)
{
var b = Expression.Parameter(typeof(Building), "b");
var property = Expression.Property(b, "Floors");
var constant = Expression.Constant(minFloors);
var body = Expression.GreaterThan(property, constant);
return Expression.Lambda<Func<Building, bool>>(body, b);
}
Leverage helper libraries: Instead of building trees by hand every time, use libraries to increase maintainability. The System.Linq.Dynamic.Core
library helps you write queries with strings like "Price < 300"
are converted into expression trees.
Conclusion
Expression trees in .NET are a powerful tool to add flexibility to your code. They allow building LINQ queries at runtime and executing them based on user input. It suits the scenario where filter fields are not defined and are dependent on the user. I shared examples of how to write a query and code with an expression tree. By following the tips discussed above, you can leverage this remarkable tool to make code more robust and maintainable.
elmah.io: Error logging and Uptime Monitoring for your web apps
This blog post is brought to you by elmah.io. elmah.io is error logging, uptime monitoring, deployment tracking, and service heartbeats for your .NET and JavaScript applications. Stop relying on your users to notify you when something is wrong or dig through hundreds of megabytes of log files spread across servers. With elmah.io, we store all of your log messages, notify you through popular channels like email, Slack, and Microsoft Teams, and help you fix errors fast.
See how we can help you monitor your website for crashes Monitor your website