Search code examples
c#expression-trees

including child expression tree with call to Enumerable.Select


I would like to use one lambda expression as the argument to the Enumerable.Select method, and add this as a new binding to a parent lambda expression as demonstrated:

Expression<Func<Bar, BarDto>> MapBar = b => new BarDto { BarInt = b.BarInt };
Expression<Func<Foo, FooDto>> MapFoo = f => new FooDto { FooInt = f.FooInt };

Expression<Func<Foo, FooDto>> expressionIWant = f => new FooDto
{
    FooInt = f.FooInt,
    Bars = f.Bars.Select(b => new BarDto { BarInt = b.BarInt })
};

where I have got to so far:

i have an ExpressionVisitor containing:

protected override Expression VisitMemberInit(MemberInitExpression node)
{
    var newBindings = new[]
    {
        Expression.Bind(_pi, _newExpr),
    };
    node = node.Update(
        node.NewExpression,
        node.Bindings.Concat(newBindings));

    return node;
}

I obviously need to include something along the lines of

var typeArgs = _originalChildExpression.Type.GenericTypeArguments;
_newExpr = Expression.Call(typeof(Enumerable),"Select",typeArgs,???source???,
    _originalChildExpression);

where ???source??? represents the f.Bars in the expressionIWant at the top. How can this be achieved? Thank you very much.


Solution

  • It's very similar to your previous question. Looking at

    Expression<Func<Foo, FooDto>> expressionIWant = f => new FooDto
    {
        FooInt = f.FooInt,
        Bars = f.Bars.Select(b => new BarDto { BarInt = b.BarInt })
    };
    

    you can see that f.Bars is a Property("Bars") of the Parameter("f").

    Hence the whole method could be something like this:

    public static class ExpressionUtils
    {
        public static Expression<Func<T, TMap>> AddCollectionMap<T, TMap, U, UMap>(
            this Expression<Func<T, TMap>> parent,
            Expression<Func<U, UMap>> nav, 
            string propName)
        {
            var parameter = parent.Parameters[0];
            var target = typeof(TMap).GetProperty(propName);
            var source = Expression.Property(parameter, propName);
            var binding = Expression.Bind(target, Expression.Call(
                typeof(Enumerable), "Select", nav.Type.GenericTypeArguments, source, nav));
            var body = parent.Body.AddMemberInitBindings(binding);
            return Expression.Lambda<Func<T, TMap>>(body, parameter);
        }
    
        static Expression AddMemberInitBindings(this Expression expression, params MemberBinding[] bindings)
        {
            return new AddMemberInitBindingsVisitor { Bindings = bindings }.Visit(expression);
        }
    
        class AddMemberInitBindingsVisitor : ExpressionVisitor
        {
            public MemberBinding[] Bindings;
            protected override Expression VisitMemberInit(MemberInitExpression node)
            {
                return node.Update(node.NewExpression, node.Bindings.Concat(Bindings));
            }
        }
    

    }

    and the sample usage:

    var expressionIGet = MapFoo.AddCollectionMap(MapBar, "Bars");