4

If I want retrieve more columns with an already existing lambda tree expression like below, how would I do that? This works with Entity Frameworks and want it to still work.

Expression<Func<DivisionTeam, DirectorTeamModel>> columns= (d) => new DirectorTeamModel
{
    Id = d.Id,
    TeamId = d.Team.Id
};

if (criteria.Template == ExportTemplate.Import || criteria.Template == ExportTemplate.Default)
{
    // Retrieve additional columns from "columns" expression tree
}

return _divisionTeamsRepository.GetPagedResults(criteria.Page, criteria.PageSize, @where.Expand(), string.Format("{0} {1}", criteria.SortOrder, criteria.SortDirection), columns);
Mike Flynn
  • 22,342
  • 54
  • 182
  • 341
  • http://stackoverflow.com/questions/16516971/linq-dynamic-select – George Vovos May 06 '17 at 17:50
  • 3
    What's wrong with `columns = d => { var c = columns(); c.OtherProperty = d.OtherProperty; return c; }`? Please be more specific about what you've tried, and what _specifically_ you are having trouble with. Your question is very broad at the moment. – Peter Duniho May 06 '17 at 18:00
  • You don't think I did that already? Looks like you didn't test that because you get this error. `A lambda expression with a statement body cannot be converted to an expression tree` – Mike Flynn May 08 '17 at 00:21
  • 1
    You'll probably enter a world of pain if you want to modify the expression directly. Your code will look a lot more readable if you just create an entirely new expression instead, even if it means duplicating the first one and only adding additional properties. – DavidG May 08 '17 at 01:31
  • Yea I figured that was my last resort but I can't believe there isn't a simple solution to this. – Mike Flynn May 08 '17 at 01:32
  • It's likely possible but it will be a lot of code, manually creating `MemberExpression`s, adding them to the bindings, doing this for every additional property you want to add and then creating a new expression anyway because much of the hierarchy is immutable anyway. – DavidG May 08 '17 at 01:35
  • Yea I saw those and just couldn't believe that was the other option. – Mike Flynn May 08 '17 at 01:36

1 Answers1

2

Given two "selector" expressions, you've to take the bindings from their MemberInitExpression and create a new expression using all the bindings. But this expression isn't going to work, since it uses two different parameter expressions for one single parameter. We need to fix that too.

Given...

Expression<Func<TSource, TResult>> left = ... // columns
Expression<Func<TSource, TResult>> right = ... // more columns

...take the bindings...

var leftInit = left.Body as MemberInitExpression;
var rightInit = right.Body as MemberInitExpression;

var bindings = leftInit.Bindings.Concat(rightInit.Bindings);

...create a new expression...

var result = Expression.Lambda<Func<TSource, TResult>>(
    Expression.MemberInit(Expression.New(typeof(TResult)), bindings), ???);

...BUT, need single parameter...

var binder = new ParameterBinder(left.Parameters[0], right.Parameters[0]);
var bindings = binder.Visit(leftInit.Bindings.Concat(rightInit.Bindings));

// now, just use right.Parameters[0] as parameter...

And, replacing parameters works well using an expression visitor:

class ParameterBinder : ExpressionVisitor
{
    readonly ParameterExpression parameter;
    readonly Expression replacement;

    public ParameterBinder(ParameterExpression parameter, Expression replacement)
    {
        this.parameter = parameter;
        this.replacement = replacement;
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        if (node == parameter)
            return replacement;

        return base.VisitParameter(node);
    }
}

Abstracting this plumbing stuff works quite well. In fact, you can just use an existing library (spoiler: I'm the author), which should lead to something like that:

var merged = columns.Apply(moreColumns);
Axel Heer
  • 1,863
  • 16
  • 22