Introduction to Expression Visitors in C#
Using an expression visitor to modify queries to handle translations
Today I needed to investigate a bug with some code that uses an expression visitor. This is a concept I had a vague awareness of, but didn't really understand in any depth. This blog post covers what I have learned about expression visitors and how to use them.
What is an expression?
The Expression
class in System.Linq.Expressions
is a class to represent an Expression Tree. This is a way of representing code as data in a tree structure, with each node of the tree being a piece of data or logic.
The most common use case in my line of work is when using Entity Framework. EF will generate an expression tree to represent each query, and this expression tree can then be converted into SQL commands with any flavour of SQL. The logic in the expression tree is independent of how that logic is eventually conveyed in SQL.
What is an expression visitor?
An expression visitor is a class which traverses an expression tree. It can then perform any arbitrary action, typically either modifying the expression tree or converting it into a meaningful output (in the case of EF, this output would be a SQL string which can be sent to the database). System.Linq.Expressions
provides an abstract ExpressionVisitor
class, which can be derived to create your own expression visitors. By overriding the Visit
method (or one of the other, more specialised methods it has), you can control what happens when the expression is visited.
Real world use case
My application is available in multiple languages. We use Entity Framework for querying data. For some of the translations, we use an expression visitor to modify the expression tree which EF generates, before it is evaluated against the database. This allows us to write queries without worrying about translations which has a number of advantages. The queries are
- quicker to write
- easier to reason about
- less error prone
All of this adds up to a more stable application, with a much reduced chance of changes which break when used in different languages.
How does the expression visitor help?
Imagine a simple EF query being executed against a DbContext
:
var query = _context.Set<Foo>()
.Select(f => new FooDto
{
Prop = f.Prop
});
The translation only affects the mapping logic, which we can extract as an expression tree:
Expression<Func<Foo, FooDto>> map = f => new FooDto
{
Prop = f.Prop
};
var query = _context.Set<Foo>()
.Select(map);
Using a expression visitor, we can rewrite the map
expression tree to add the logic for the translation. I won't go into detail about that here as it's out of scope for this article, but the gist is that we create an expression visitor, then pass it the expression tree.
This is the basic form of the expression visitor:
class FooExpressionVisitor : ExpressionVisitor
{
public override Expression Visit(Expression node)
{
// custom logic goes here
return base.Visit(node);
}
}
This is then very simple to use:
var fooExpressionVisitor = new FooExpressionVisitor();
var modifiedMap = fooExpressionVisitor.Visit(map)
as Expression<Func<Foo, FooDto>>;
var query = _context.Set<Foo>()
.Select(modifiedMap);
Conclusion
Expression Visitors are powerful ways to manipulate logic within your code. A real world use case is to rewrite an Entity Framework query before executing against the database, but they can be used for a wide variety of applications.