Reputation: 1604
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
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);
}
}
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
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
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