How to re-wrap a Linq Expression Tree

c# expression expression-trees linq metaprogramming

Question

I have an Expression<Func<Entity, string>> that can be either a property or a nested property accessor

y => y.SearchColumn

or

y => y.SubItem.SubColumn

I am building up an expression tree dynamically and would like to get an InvokeExpression like this

x => x.SearchColumn.Contains("foo");
x => x.Sub.SearchColumn.Contains("foo");

I'm pretty sure I can unwrap the Expression body, then partially apply the x ParameterExpression to it but I can't figure out the magical incantations to make this happen

So I have something like

Expression<Func<Entity, string>> createContains(Expression<Func<Entity, string>> accessor) {
    var stringContains = typeof(String).GetMethod("Contains", new [] { typeof(String) });
    var pe = Expression.Parameter(typeof(T), "__x4326");
    return Expression.Lambda<Func<Entity, bool>>(
        Expression.Call(
            curryExpression(accessor.Body, pe),
            stringContains,
            Expression.Constant("foo")
        )
        , pe
    );              
}

    static Expression curryExpression(Expression from, ParameterExpression parameter) {
        // this doesn't handle the sub-property scenario
        return Expression.Property(parameter, ((MemberExpression) from).Member.Name);
        //I thought this would work but it does not
        //return Expression.Lambda<Func<Entity,string>>(from, parameter).Body;
    }

Edit: Here is the full thing I'm trying to do along with the error

Accepted Answer

You need to do two things - first one you can use the same accessor.Body, but it will reference to incorrect Parameter, as you created a new one. Second one you need to write custom ExpressionVisitor that will replace all usage of original y ParameterExpression to a new created, so result expression will be compiled fine.

If you don't need to create new ParameterExpression, then you can just use the same accessor.Body and resent original ParameterExpression to a new tree.

So here is my test working copy with a new ParameterExpression - https://dotnetfiddle.net/uuPVAl

And the code itself

public class Program
{
    public static void Main(string[] args)
    {
        Expression<Func<Entity, string>> parent = (y) => y.SearchColumn;
        Expression<Func<Entity, string>> sub = (y) => y.Sub.SearchColumn;

        var result = Wrap(parent);
        Console.WriteLine(result);
        result.Compile();

        result = Wrap(sub);
        Console.WriteLine(result);
        result.Compile();

        result = Wrap<Entity>((y) => y.Sub.Sub.Sub.SearchColumn);
        Console.WriteLine(result);
        result.Compile();

    }

    private static Expression<Func<T, bool>> Wrap<T>(Expression<Func<T, string>> accessor)
    {
        var stringContains = typeof (String).GetMethod("Contains", new[] {typeof (String)});
        var pe = Expression.Parameter(typeof (T), "__x4326");
        var newBody = new ParameterReplacer(pe).Visit(accessor.Body);
        var call = Expression.Call(
            newBody,
            stringContains,
            Expression.Constant("foo")
            );
        return Expression.Lambda<Func<T, bool>>(call, pe);
    }
}

public class ParameterReplacer : ExpressionVisitor
{
    private ParameterExpression _target;

    public ParameterReplacer(ParameterExpression target)
    {
        _target = target;
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        // here we are replacing original to a new one
        return _target;
    }
}

public class Entity
{
    public string SearchColumn { get; set; }

    public Entity Sub { get; set; }
}

PS: this example will work only if you have only one ParameterExpression in original query, otherwise visitor should differentiate them

UPDATE

Here is my working answer with your full example in update - https://dotnetfiddle.net/MXP7wE


Popular Answer

You just need to a fix a couple of things:

  • Return type of your method should be Expression<Func<T, bool>>.
  • The 1st parameter to Expression.Call() should simply be accessor.Body.
  • The ParameterExpression parameter to the Expression.Lambda<Func<T, bool>>() method call should simply be set from the accessor's parameter.

Method:

Expression<Func<T, bool>> CreateContains<T>(Expression<Func<T, string>> accessor)
{
    var stringContains = typeof(String).GetMethod("Contains", new[] { typeof(String) });
    return Expression.Lambda<Func<T, bool>>(
        Expression.Call(
            accessor.Body,
            stringContains,
            Expression.Constant("foo")
        )
        , accessor.Parameters[0]
    );
}


Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why