Expression Visitor to be used on other type

204 views Asked by At

I'm trying to use an ExpressionVisitor to change a expression to be used on another underlying type.

Assume I have 2 class Foo and Bar, where Bar is a logical representation and Foo is a EF entity.

So I have a method that accepts an expression on Bar, executes a query on Foo

IEnumeable<Bar> Where(Expression<Func<Bar, bool>> barExpression){
     // Do some magic
     var fooExpression = (Expression<Func<Foo, bool>>) MyExpressionVisitor.Visit(barExpression);
     // Execute on DbSet<Foo>
     var foos = _context.Foos.Where(fooExpression);
     return foos.Select(MapToBar)
}

I did find an answer that could do this: https://stackoverflow.com/a/46592531/2968001

So far so good, however, Bar does contain nested properties where Foo is Flat.

For example:

class Foo {
    string MyProperty {get; set; }
}

class Bar {
    Baz Baz { get; set; }  
}

class Baz {
   string SomeProperty {get; set; }
}

If I now do something like

.Where(bar => bar.Baz.SomeProperty.EndsWith("789");

I would like the fooExpression to be the equivalent of

foo => foo.MyProperty.EndsWith("789")

And I can not get my head around it. While I do think that at some point I got the right expression build (when the visiting node is of type Foo), It get not applied in the final Lambda

1

There are 1 answers

0
NetMage On

Given your Where method as follows:

public static IQueryable<Foo> Where(Expression<Func<Bar, bool>> barExpression) {
    // Do some magic
    var fooExpression = BarToFooVisitor.Convert(barExpression);

    // Execute on DbSet<Foo>
    var foos = _context.Foos.Where(fooExpression);
    return foos; //.Select(MapToBar);
}

Then you can write an ExpressionVisitor to replace the parameter in a Func<Bar,bool> predicate and convert any property accessors to use the new parameter and mapped Foo property:

public class BarToFooVisitor : ExpressionVisitor {
    ParameterExpression barParm;
    ParameterExpression fooParm;
    public BarToFooVisitor(ParameterExpression _barParm, ParameterExpression _fooParm) => (barParm, fooParm) = (_barParm, _fooParm);

    protected override Expression VisitMember(MemberExpression me) {
        if (me.Expression.Type == typeof(Baz)) { // we are at Baz.Someproperty ; could also test me.Expression.Member.Name == nameof(Bar.Baz)
            if (((MemberExpression)me.Expression).Expression == barParm) { // we are at b.Baz
                me = Expression.Property(fooParm, "MyProperty");
            }
        }
        return me;
    }

    public static Expression<Func<Foo, bool>> Convert(Expression<Func<Bar, bool>> e) {
        // (Foo f) =>
        var fParm = Expression.Parameter(typeof(Foo), "f");
        // (Bar b) =>
        var bParm = e.Parameters[0];
        // func_Foo
        var body = new BarToFooVisitor(bParm, fParm).Visit(e.Body);
        // (Foo f) => func_Foo(f)
        return Expression.Lambda<Func<Foo, bool>>(body, fParm);
    }
}