How to use ExpressionVisitor like a pro?

Recently I had to deal with ExpressionVisitorclass in C#. It is pretty easy concept as long as you understand ExpressionTrees. Basically, if you have ever created expression at runtime (equivalent of: Expression<Func<T, object>> expr = el => el.SomeProperty, but this one is build at compile time), you will be able to handle ExpressionVisitor class. It won't be the easiest experience ever, but definitely manageable. 

What for it can be used? Definitely there are various way to utilize this functionality. The first one I can think of is when you want to create your own ORM as this is the way to translate this what you define in the code to SQL. Sometimes you may have to dynamically add a condition to an expression you pass to a method or you want to completely change it depending on some other, dynamic factors. That's the second usage I can think of now. 

So what happens to our <expression> during compile time? How the compiler constructs the lambda in the code, so the runtime is able to execute it? Check the code below

image

Nice one, don't you think?

Imagine something more complex. Something like a filter

Expression<Func<T, bool>> expr = el => el.SomeProperty == "abc" && el.AnotherProperty >= 123

image

This is the amount of operations the compiler has to do when it strips down your lambda you created in the code. 

Now, you basically know how expressions (or lambdas as this is the more popular name, even though Lambda is only one type of Expression) work and how they are constructed by the compiler. So what to do when you want to translate this to SQL? 

ExpressionVisitor as a SQL translator

Below you can see a shortened version of a simple Translator that translates Lambda to SQL. If you want to see the full version of the file, use this link

public class LambdaToSqlTranslator : ExpressionVisitor
{
    private StringBuilder _sqlAccumulator;

    public string Translate(Expression expression)
    {
        this._sqlAccumulator = new StringBuilder();
        this.Visit(expression);
        
        return this._sqlAccumulator.ToString();
    }

    protected override Expression VisitBinary(BinaryExpression b)
    {
        _sqlAccumulator.Append("(");
        this.Visit(b.Left);

        switch (b.NodeType)
        {
            case ExpressionType.And:
            case ExpressionType.AndAlso:
                _sqlAccumulator.Append(" AND ");
                break;
            case ExpressionType.Or:
            case ExpressionType.OrElse:
                _sqlAccumulator.Append(" OR ");
                break;
                _sqlAccumulator.Append(" OR ");
                break;
            ...
            case ExpressionType.GreaterThan:
                _sqlAccumulator.Append(" > ");
                break;
            case ExpressionType.GreaterThanOrEqual:
                _sqlAccumulator.Append(" >= ");
                break;
            default:
                throw new NotSupportedException(string.Format("The binary operator '{0}' is not supported", b.NodeType));
        }

        this.Visit(b.Right);
        _sqlAccumulator.Append(")");
        
        return b;
    }
}

To use it, create the instance of an object and pass lambda. As a result you'll get a sql statement

var translator = new LambdaToSqlTranslator();

Expression<Func<SomeClass, bool>> whereQuery = el => el.SomeProperty != null && el.SomeProperty == "abc" && el.AnotherProperty >= 123;

var sql = translator.Translate(whereLambda);

 

When you dump sql value, you'll see this:
(((SomeProperty IS NOT NULL) AND (SomeProperty = 'abc')) AND (AnotherProperty >= 123))

Quite cool, isn't it? 🙂

ExpressionVisitor as a helper to change existing expressions

Now, imagine you have such a predicate expression which is passed next to sql through your layer which then executes ORM library

Expression<Func<SomeClass, bool>> expression = el => el.SomeProperty == "ABC" && el.AnotherProperty > 123

The new requirement for the software is to show only active entities and all inactive should be available only for other departments which have the database copied and they prepare reports for the business.

Obviously, you can go through the code base, find every single place which constructs lambda and add there

&& el.Active

It'll definitely work, but the amount of work required may be enormous, depending on the size of the application. With ExpressionVisitor you can add (or remove/modify) such condition during runtime. Why not to change the existing expression? Because it is immutable! However, using with the visitor pattern, the expression tree is traversed from the top to the bottom and reconstructed again. This creates the opportunity to change the expression by adding additional clause to it.

If we define the following class:

public class ActiveOnlyExpressionVisitor : ExpressionVisitor
{
    public override Expression Visit(Expression node)
    {
        var lambdaExpressionOfNode = (LambdaExpression)node;
        var parameterExpression = (ParameterExpression)GetParameterExpression(node);
        
        var activePropertyExpression = Expression.PropertyOrField(parameterExpression, nameof(SomeClass.Active));
        var andExpression = Expression.And(lambdaExpressionOfNode.Body, Expression.Equal(activePropertyExpression, Expression.Constant(true)));

        var newLambdaExpression = Expression.Lambda>(andExpression, new ParameterExpression[] {parameterExpression});

        return newLambdaExpression;
    }
    
    private static Expression GetParameterExpression(Expression expression)
    {
        if (expression.NodeType == ExpressionType.Lambda)
        {
            var lambdaExpression = (LambdaExpression)expression;
            var parameterExpression = lambdaExpression.Parameters.FirstOrDefault(x => x.NodeType == ExpressionType.Parameter);
            return parameterExpression;
        }
        
        return null;
    }
}

and use it this way:

Expression<Func<SomeClass, bool>> expression = el => el.AnotherProperty == 123;

var newExpression = new ActiveOnlyExpressionVisitor().Visit(expression);
var sql = new LambdaToSqlTranslator().Translate(newExpression);

it'll produce the following sql statement:

((AnotherProperty = 123) AND (Active = 1))

Voila, you dynamically added Active = 1 to the lambda which was then translated to an appropriate SQL statement! Quite easy, don't you think? 🙂

ExpressionVisitor together with Expression Trees aren't so easy to understand, but the more you practice, the more you'll understand. To be honest, whenever I have to manipulate Expression Trees it makes me anxious, but I don't give up and just open LinqPad and I find the way to achieve what I want.

Happy coding! 🙂

Leave a Comment