Expression Trees and the Specification Pattern

Expression Tree

Most .NET developers use Entity Framework Core every day, but few realize that expression trees are one of the technologies powering LINQ queries and SQL generation behind the scenes. As applications grow, query logic and business rules become increasingly complex, which is where the Specification pattern comes into play.

The Specification pattern builds on top of expression trees to encapsulate filtering and business rules into reusable, maintainable components. This makes it a popular choice in Clean Architecture, CQRS, and Domain-Driven Design applications.

Although expression trees and specifications are widely used in modern .NET development, they are often misunderstood. Expression trees are a C# language feature, while the Specification pattern originates from Domain-Driven Design and is not part of the original Gang of Four (GoF) design patterns.

In this article, we will explore how expression trees work, how Entity Framework translates them into SQL, and how the Specification pattern leverages them to build cleaner applications.

What Is an Expression Tree?

An expression tree represents code as data. Normally, a lambda expression executes immediately:

Func<int, int> square = x => x * x;
Console.WriteLine(square(5));

Output:

25

However, expression trees store the code structure instead of executing it.

using System.Linq.Expressions;
Expression<Func<int, int>> square = x => x * x;
Console.WriteLine(square);

Output:

x => (x * x)

Internally, C# converts this lambda into a tree-like structure.

Lambda
 └── Multiply
     ├── Parameter(x)
     └── Parameter(x)

Each node is represented by classes such as:

  • LambdaExpression
  • BinaryExpression
  • ParameterExpression
  • ConstantExpression
  • MethodCallExpression

Why Does Entity Framework Need Expression Trees?

Suppose we have the following entity:

public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Department { get; set; }
    public decimal Salary { get; set; }
    public bool IsActive { get; set; }
}

We want to search employees dynamically.

var employees = context.Employees
    .Where(e => e.Department == "IT")
    .ToList();

Because DbSet<Employee> implements IQueryable<Employee>, Entity Framework does not receive a regular delegate.

Instead, it receives:

Expression<Func<Employee, bool>>

Internally:

Lambda
   │
   └── Equal
         ├── Employee.Department
         └── "IT"

Entity Framework reads this tree and converts it into SQL:

SELECT *
FROM Employees
WHERE Department = 'IT';

Without expression trees, EF Core would have no way to translate C# code into SQL.


Func<T> vs Expression<Func<T>>

Although these two declarations look similar, they behave very differently.

Func<Employee, bool> func =
    e => e.Department == "IT";

Expression<Func<Employee, bool>> expr =
    e => e.Department == "IT";

Func<T>

  • Compiled into executable code.
  • Runs in memory.
  • Cannot be converted to SQL.

Expression<Func<T>>

  • Stored as a tree.
  • Can be inspected.
  • Can be translated into SQL.

The Problem with Large Repositories

Many applications start with repositories like this:

public async Task<List<Employee>> SearchAsync(
    EmployeeFilter filter)
{
    IQueryable<Employee> query = _context.Employees;

    if (!string.IsNullOrWhiteSpace(filter.Department))
    {
        query = query.Where(
            e => e.Department == filter.Department);
    }

    if (filter.IsActive.HasValue)
    {
        query = query.Where(
            e => e.IsActive == filter.IsActive.Value);
    }

    if (filter.MinSalary.HasValue)
    {
        query = query.Where(
            e => e.Salary >= filter.MinSalary.Value);
    }

    return await query.ToListAsync();
}

Initially, this works well.

However, after a few months, business requirements grow:

  • Active employees.
  • Employees in the IT department.
  • Employees earning more than ₹50,000.
  • Active IT employees.
  • Active IT employees with high salaries.

Repositories quickly become difficult to maintain.


Enter the Specification Pattern

The Specification pattern encapsulates business rules into reusable classes.

Instead of creating many repository methods:

GetActiveEmployees()

GetEmployeesByDepartment()

GetHighSalaryEmployees()

GetActiveITEmployees()

we create specifications.


Creating a Specification Interface

using System.Linq.Expressions;

public interface ISpecification<T>
{
    Expression<Func<T, bool>> Criteria { get; }
}

Notice that the specification uses:

Expression<Func<T, bool>>

This is where expression trees and specifications become connected.


Creating a Specification

public sealed class ActiveEmployeeSpecification
    : ISpecification<Employee>
{
    public Expression<Func<Employee, bool>> Criteria =>
        e => e.IsActive;
}

Another example:

public sealed class ITEmployeeSpecification
    : ISpecification<Employee>
{
    public Expression<Func<Employee, bool>> Criteria =>
        e => e.Department == "IT";
}

Repository Implementation

public interface IReadRepository<T>
{
    Task<List<T>> GetAsync(
        ISpecification<T> specification);
}

Implementation:

public async Task<List<T>> GetAsync(
    ISpecification<T> specification)
{
    return await _context.Set<T>()
        .Where(specification.Criteria)
        .ToListAsync();
}

Usage:

var specification =
    new ITEmployeeSpecification();

var employees =
    await repository.GetAsync(specification);

How Are Expression Trees and Specifications Related?

The Specification pattern is essentially a design pattern built on top of expression trees.

The relationship looks like this:

Expression Tree
        ↑
        │
Specification Pattern
        ↑
        │
Repository
        ↑
        │
Entity Framework Core
        ↑
        │
SQL

The specification stores an expression tree:

Criteria = e =>
    e.IsActive &&
    e.Department == "IT";

Entity Framework reads that expression tree and generates SQL:

SELECT *
FROM Employees
WHERE IsActive = 1
    AND Department = 'IT';

Why Not Use Func<T> Instead?

Suppose our specification looked like this:

public interface ISpecification<T>
{
    Func<T, bool> Criteria { get; }
}

The query would execute in memory:

var employees = await context.Employees.ToListAsync();

var result =
    employees.Where(e => e.Department == "IT");

This would force the application to load every row from the database before filtering.

For large tables, this becomes extremely expensive.

That is why Specification implementations almost always use:

Expression<Func<T, bool>>

instead of:

Func<T, bool>

When Should You Use the Specification Pattern?

Use specifications when:

✅ Query logic is reused.

✅ You are using generic repositories.

✅ Your application follows Clean Architecture.

✅ Your project uses CQRS.

✅ You need complex filtering rules.

Avoid specifications when:

❌ The application is small.

❌ The project is simple CRUD.

❌ Queries are never reused.


Summary

Expression trees are one of the most important but least understood features of modern .NET development.

They power:

  • Entity Framework Core
  • LINQ providers
  • AutoMapper
  • Dynamic queries
  • Specifications

The Specification pattern, although not part of the Gang of Four catalog, has become a cornerstone of Domain-Driven Design and Clean Architecture.

If you understand expression trees, you will understand how Entity Framework translates C# code into SQL. And if you understand specifications, you will write cleaner, more maintainable query logic in large applications.

Share Button

Leave a Reply

Your email address will not be published. Required fields are marked *