Declare variable in LinQ expression tree

.net, c#, expression, lambda, linq

Solution

So what we can do here is create a `Compose` method that takes an `Expression` representing a lambda with one parameter and a return value, and then a second lambda that takes the first lambda's output as it's input, and then returns a new lambda that takes the first parameter's input and returns the second parameter's output.

If it were just regular delegates the method would look something like this (just to give you some idea of what it's conceptually doing):

public static Func<TFirst, TResult> Compose<TFirst, TIntermediate, TResult>(
    Func<TFirst, TIntermediate> first,
    Func<TIntermediate, TResult> second)
{
    return firstParam => second(first(firstParam));
}

This, implemented in `Expression` objects, is a touch more complex:

public static Expression<Func<TFirstParam, TResult>>
    Compose<TFirstParam, TIntermediate, TResult>(
    this Expression<Func<TFirstParam, TIntermediate>> first,
    Expression<Func<TIntermediate, TResult>> second)
{
    var param = Expression.Parameter(typeof(TFirstParam), "param");

    var newFirst = first.Body.Replace(first.Parameters[0], param);
    var newSecond = second.Body.Replace(second.Parameters[0], newFirst);

    return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}

This uses the following method to replace all instances of one `Expression` with another:

internal class ReplaceVisitor : ExpressionVisitor
{
    private readonly Expression from, to;
    public ReplaceVisitor(Expression from, Expression to)
    {
        this.from = from;
        this.to = to;
    }
    public override Expression Visit(Expression node)
    {
        return node == from ? to : base.Visit(node);
    }
}

public static Expression Replace(this Expression expression,
    Expression searchEx, Expression replaceEx)
{
    return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}

Now that we have our `Compose` method we can write an expression that takes an item of yours and returns a `Contract`, and then write another method that takes that contract and computes a `bool` indicating if it's valid:

public static Expression<Func<Item, bool>> GetFilter(
    TimeSpan fromValue, TimeSpan toValue)
{
    Expression<Func<Item, Contract>> currentContract =
        q => q.StaffContracts
                .OrderByDescending(p => p.SignedDate)
                .Where(p => p.Active)
                .FirstOrDefault();

    return currentContract.Compose(contract =>
        contract != null &&
        contract.TimeSpan >= fromValue &&
        contract.TimeSpan <= toValue);
}

The `Compose` method here will, internally, replace all instances of `contract` in the second lambda with the body of `currentContract`. So the effect is if you had written it out three times, even though this prevents you from needing to do that.

This `Compose` method is something that you can use anytime you would want to create a variable in an expression tree (something that query providers do not support). You can always create a method that computes what the value of the variable should be, and then `Compose` that to use it within another expression.

Problem

In a previous question (Refine Enumerable LINQ WHERE statement with repeated parameters), I solve the problem by declaring variable using `let` statement, and my code looks like this: ``` return from q in pList let currentContract = q.StaffContracts.OrderByDescending(p => p.SignedDate).Where(p => p.Active).FirstOrDefault() where (currentContract.Timespan >= fromValue && currentContract.Timespan <= toValue) select q; ``` In this case, `currentContract` is the repeated expression. Now I have a new problem. My method doesn't provide nor request `IQueryable` anymore, but it requires an `Expression<Func<Staff, bool>>` as return instead (meaning, only the WHERE clause). I tried this but it is not successful: ``` return q => { var currentContract = q.StaffContracts.OrderByDescending(p => p.SignedDate).Where(p => p.Active).FirstOrDefault(); return currentContract != null && currentContract.Timespan >= fromValue && currentContract.Timespan <= toValue; }; ``` The compile error message is: A lambda expression with a statement body cannot be converted to an expression tree Is there any workaround with this?

Original source

Related problems