Mostafa Soghandi
Mostafa Soghandi

Reputation: 1604

How to add another condition to an expression?

I have an expression like this:

Expression<Func<int, bool>> exp = i => i<15 && i>10;

I want to add a condition to exp after this line. How can I do this?

Upvotes: 22

Views: 18961

Answers (3)

Hassan Faghihi
Hassan Faghihi

Reputation: 2031

I found the answer from https://entityframework.net/ for .Net Framework 6.0

which also worked for me with .net core

class Program
{
    static void Main(string[] args)
    {
        Expression<Func<int, bool>> exprA = a => a == 3;
        Expression<Func<int, bool>> exprB = b => b == 4;
        Expression<Func<int, bool>> exprC =
            Expression.Lambda<Func<int, bool>>(
                Expression.OrElse(
                    exprA.Body,
                    new ExpressionParameterReplacer(exprB.Parameters, exprA.Parameters).Visit(exprB.Body)),
                exprA.Parameters);
        Console.WriteLine(exprA.ToString());
        Console.WriteLine(exprB.ToString());
        Console.WriteLine(exprC.ToString());
        Func<int, bool> funcA = exprA.Compile();
        Func<int, bool> funcB = exprB.Compile();
        Func<int, bool> funcC = exprC.Compile();
        Debug.Assert(funcA(3) && !funcA(4) && !funcA(5));
        Debug.Assert(!funcB(3) && funcB(4) && !funcB(5));
        Debug.Assert(funcC(3) && funcC(4) && !funcC(5));
    }
}

Note that: ExpressionParameterReplacer is a helper class that you should put it in your helpers or anywhere accessible, and does not exists in standard packages.

public class ExpressionParameterReplacer : ExpressionVisitor
{
    public ExpressionParameterReplacer(IList<ParameterExpression> fromParameters, IList<ParameterExpression> toParameters)
    {
        ParameterReplacements = new Dictionary<ParameterExpression, ParameterExpression>();
        for (int i = 0; i != fromParameters.Count && i != toParameters.Count; i++)
            ParameterReplacements.Add(fromParameters[i], toParameters[i]);
    }

    private IDictionary<ParameterExpression, ParameterExpression> ParameterReplacements { get; set; }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        ParameterExpression replacement;
        if (ParameterReplacements.TryGetValue(node, out replacement))
            node = replacement;
        return base.VisitParameter(node);
    }
}

Sample in my own scenario

My normal usage of this was to add multiple conditions together in one of my services, which I do not have direct access to the Query so I cannot use multiple .Where() functions:

Expression<Func<Order, bool>> filter = w => ...;

// Extra complex filters which I do not feed to my request models
Expression<Func<Order, bool>> filter2 = null;
switch (model.PredefinedFilter)
{
    case OrderPredefinedFilterEnum.SupportPending:
        filter2 = w => 
            (
                (w.Cart.CartFlow == CartFlowEnum.Buyer_First_Order_Request && w.Cart.CartStatus == CartStatusEnum.PaidByBuyer) || 
                (w.Cart.CartFlow == CartFlowEnum.Seller_First_Suggestion && w.Cart.CartStatus == CartStatusEnum.WaitingForPaymentConfirmByBuyer)
            ) && 
            w.Cart.CartSupportStatus == CartSupportStatusEnum.Waiting;
        break;
}

if(filter2 != null)
{
    filter = Expression.Lambda<Func<Order, bool>>(
        Expression.AndAlso(filter.Body, new ExpressionParameterReplacer(filter2.Parameters, filter.Parameters).Visit(filter2.Body)), 
        filter.Parameters[0]);
}

result = (await _orderRepository.GetAllAsNoTrackingAsync(
    a => totalCount = a,
    filter,
    selector,
    OrderBy,
    take,
    skip,
    cancellationToken: cancellationToken))
    .ToList();

Upvotes: 1

BartoszKP
BartoszKP

Reputation: 35901

Simply with this:

Expression<Func<int, bool>> exp = i => i < 15 && i > 10;
var compiled = exp.Compile();
exp = i => compiled(i) && i % 2 == 0;  //example additional condition

Note that you can't do it like this:

exp = i => exp.Compile()(i) && i % 2 == 0; //example additional condition

because exp will be added to the closure by reference and as a result, calling it will cause a StackOverflowException.

Upvotes: 25

Georg
Georg

Reputation: 5791

You have two options. The first one is the version of BartoszKP, to blackbox the first expression and use it afterwards. However, while this has a great syntax support, it also means that systems like the Entity Framework cannot really use the expression, because it is blackboxed. If this expression was used in a database query, the EF could not check this predicate on the server, but has to retrieve all the data to the client, if it works at all.

Thus, if you want to use the expression e.g. for a database query, you have to use the Expression API, i.e.

Expression<Func<int, bool>> exp = i => i<15 && i>10;
exp = Expression.Lambda<Func<int, bool>>(Expression.AndAlso(exp.Body, ...), exp.Parameters[0]);

The three dots indicate the expression that you want to insert as second part. You could use another expression created by the compiler, but you would then have to replace the parameters.

Upvotes: 8

Related Questions