Composing Expressions in C#

This post follows on from Reusing predicates in Entity Framework.

In my previous post I introduced the "and" extension method which allowed me to create a new entity framework safe expression from two existing expressions. Here's how we make that happen:

The code is from a blogpost from Colin Meek from 2008: InvocationExpression and LINQ to Entities.

I'll just dump my version of the code here. The idea is exactly the same (even using the visitor as an immutable stack):

public class InvocationExpander : ExpressionVisitor
{
    private readonly ParameterExpression _parameter;
    private readonly Expression _expansion;
    private readonly InvocationExpander _previous;


    public static T Expand<T>(T expr)
        where T : LambdaExpression
    {
        return (T)new InvocationExpander().Visit(expr);
    }

    public InvocationExpander() { }

    private InvocationExpander(ParameterExpression parameter, Expression expansion, InvocationExpander previous)
    {
        _parameter = parameter;
        _expansion = expansion;
        _previous = previous;
    }

    public InvocationExpander Push(ParameterExpression parameter, Expression expansion)
    {
        return new InvocationExpander(parameter, expansion, this);
    }

    protected override Expression VisitInvocation(InvocationExpression iv)
    {
        if (iv.Expression.NodeType != ExpressionType.Lambda)
        {
            return base.VisitInvocation(iv);
        }
        var lambda = (LambdaExpression)iv.Expression;
        return lambda
            .Parameters
            .Select((x, i) => new { Parameter = x, Expansion = iv.Arguments[i] })
            .Aggregate(this, (previous, pair) => previous.Push(pair.Parameter, pair.Expansion))
            .Visit(lambda.Body);
    }

    protected override Expression VisitParameter(ParameterExpression p)
    {
        var expander = this;
        while (null != expander)
        {
            if (expander._parameter == p)
            {
                return base.Visit(expander._expansion);
            }
            expander = expander._previous;
        }
        return base.VisitParameter(p);
    }

}

So, now we've got that, we can create our extensions for Expressions:


// https://github.com/juanplopes/simple/blob/master/src/Simple/Expressions/PredicateBuilder.cs
public static class ExpressionExtensions
{
    public static Expression<Func<T, bool>> True<T>()
    {
        return True<T>(Expression.Parameter(typeof(T), "f"));
    }

    public static Expression<Func<T, bool>> True<T>(ParameterExpression param)
    {
        return Expression.Lambda<Func<T, bool>>(Expression.Constant(true), param);
    }

    public static Expression<Func<T, bool>> False<T>()
    {
        return False<T>(Expression.Parameter(typeof(T), "f"));
    }
    
    public static Expression<Func<T, bool>> False<T>(ParameterExpression param)
    {
        return Expression.Lambda<Func<T, bool>>(Expression.Constant(false), param);
    }

    public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
    {
        var invokedExpr = Expression.Invoke(second, first.Parameters);
        return Expression.Lambda<Func<T, bool>>
              (Expression.AndAlso(first.Body, invokedExpr), first.Parameters).ExpandInvocations();
    }

    public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
    {
        var invokedExpr = Expression.Invoke(second, first.Parameters);
        return Expression.Lambda<Func<T, bool>>
              (Expression.OrElse(first.Body, invokedExpr), first.Parameters).ExpandInvocations();
    }

    public static Expression<T> ExpandInvocations<T>(this Expression<T> expr)
    {
        return InvocationExpander.Expand(expr);
    }
}

With that code in place, we can do:

context.BlogPosts.Where(
    isDeleted
    .And(isByBar)
    .And(containsFoo));

And it will all just work!

Comments

Popular posts from this blog

Trimming strings in action parameters in ASP.Net Web API

Full text search in Entity Framework 6 using command interception