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);
7
  • stackoverflow.com/questions/16516971/linq-dynamic-select Commented May 6, 2017 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. Commented May 6, 2017 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 Commented May 8, 2017 at 0: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. Commented May 8, 2017 at 1:31
  • Yea I figured that was my last resort but I can't believe there isn't a simple solution to this. Commented May 8, 2017 at 1:32

1 Answer 1

2
+25

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);
Sign up to request clarification or add additional context in comments.

1 Comment

@Mike does that help? (Your bounty is ending within 2 days...)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.