Combining two expressions (Expression<Func<T, bool>>)
Solution 1
Well, you can use Expression.AndAlso
/ OrElse
etc to combine logical expressions, but the problem is the parameters; are you working with the same ParameterExpression
in expr1 and expr2? If so, it is easier:
var body = Expression.AndAlso(expr1.Body, expr2.Body);
var lambda = Expression.Lambda<Func<T,bool>>(body, expr1.Parameters[0]);
This also works well to negate a single operation:
static Expression<Func<T, bool>> Not<T>(
this Expression<Func<T, bool>> expr)
{
return Expression.Lambda<Func<T, bool>>(
Expression.Not(expr.Body), expr.Parameters[0]);
}
Otherwise, depending on the LINQ provider, you might be able to combine them with Invoke
:
// OrElse is very similar...
static Expression<Func<T, bool>> AndAlso<T>(
this Expression<Func<T, bool>> left,
Expression<Func<T, bool>> right)
{
var param = Expression.Parameter(typeof(T), "x");
var body = Expression.AndAlso(
Expression.Invoke(left, param),
Expression.Invoke(right, param)
);
var lambda = Expression.Lambda<Func<T, bool>>(body, param);
return lambda;
}
Somewhere, I have got some code that re-writes an expression-tree replacing nodes to remove the need for Invoke
, but it is quite lengthy (and I can't remember where I left it...)
Generalized version that picks the simplest route:
static Expression<Func<T, bool>> AndAlso<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
// need to detect whether they use the same
// parameter instance; if not, they need fixing
ParameterExpression param = expr1.Parameters[0];
if (ReferenceEquals(param, expr2.Parameters[0]))
{
// simple version
return Expression.Lambda<Func<T, bool>>(
Expression.AndAlso(expr1.Body, expr2.Body), param);
}
// otherwise, keep expr1 "as is" and invoke expr2
return Expression.Lambda<Func<T, bool>>(
Expression.AndAlso(
expr1.Body,
Expression.Invoke(expr2, param)), param);
}
Starting from .NET 4.0, there is the ExpressionVisitor
class which allows you to build expressions that are EF safe.
public static Expression<Func<T, bool>> AndAlso<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var parameter = Expression.Parameter(typeof (T));
var leftVisitor = new ReplaceExpressionVisitor(expr1.Parameters[0], parameter);
var left = leftVisitor.Visit(expr1.Body);
var rightVisitor = new ReplaceExpressionVisitor(expr2.Parameters[0], parameter);
var right = rightVisitor.Visit(expr2.Body);
return Expression.Lambda<Func<T, bool>>(
Expression.AndAlso(left, right), parameter);
}
private class ReplaceExpressionVisitor
: ExpressionVisitor
{
private readonly Expression _oldValue;
private readonly Expression _newValue;
public ReplaceExpressionVisitor(Expression oldValue, Expression newValue)
{
_oldValue = oldValue;
_newValue = newValue;
}
public override Expression Visit(Expression node)
{
if (node == _oldValue)
return _newValue;
return base.Visit(node);
}
}
Solution 2
You can use Expression.AndAlso / OrElse to combine logical expressions, but you have to make sure the ParameterExpressions are the same.
I was having trouble with EF and the PredicateBuilder so I made my own without resorting to Invoke, that I could use like this:
var filterC = filterA.And(filterb);
Source code for my PredicateBuilder:
public static class PredicateBuilder {
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> a, Expression<Func<T, bool>> b) {
ParameterExpression p = a.Parameters[0];
SubstExpressionVisitor visitor = new SubstExpressionVisitor();
visitor.subst[b.Parameters[0]] = p;
Expression body = Expression.AndAlso(a.Body, visitor.Visit(b.Body));
return Expression.Lambda<Func<T, bool>>(body, p);
}
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> a, Expression<Func<T, bool>> b) {
ParameterExpression p = a.Parameters[0];
SubstExpressionVisitor visitor = new SubstExpressionVisitor();
visitor.subst[b.Parameters[0]] = p;
Expression body = Expression.OrElse(a.Body, visitor.Visit(b.Body));
return Expression.Lambda<Func<T, bool>>(body, p);
}
}
And the utility class to substitute the parameters in a lambda:
internal class SubstExpressionVisitor : System.Linq.Expressions.ExpressionVisitor {
public Dictionary<Expression, Expression> subst = new Dictionary<Expression, Expression>();
protected override Expression VisitParameter(ParameterExpression node) {
Expression newValue;
if (subst.TryGetValue(node, out newValue)) {
return newValue;
}
return node;
}
}
Solution 3
If you provider does not support Invoke and you need to combine two expression, you can use an ExpressionVisitor to replace the parameter in the second expression by the parameter in the first expression.
class ParameterUpdateVisitor : ExpressionVisitor
{
private ParameterExpression _oldParameter;
private ParameterExpression _newParameter;
public ParameterUpdateVisitor(ParameterExpression oldParameter, ParameterExpression newParameter)
{
_oldParameter = oldParameter;
_newParameter = newParameter;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (object.ReferenceEquals(node, _oldParameter))
return _newParameter;
return base.VisitParameter(node);
}
}
static Expression<Func<T, bool>> UpdateParameter<T>(
Expression<Func<T, bool>> expr,
ParameterExpression newParameter)
{
var visitor = new ParameterUpdateVisitor(expr.Parameters[0], newParameter);
var body = visitor.Visit(expr.Body);
return Expression.Lambda<Func<T, bool>>(body, newParameter);
}
[TestMethod]
public void ExpressionText()
{
string text = "test";
Expression<Func<Coco, bool>> expr1 = p => p.Item1.Contains(text);
Expression<Func<Coco, bool>> expr2 = q => q.Item2.Contains(text);
Expression<Func<Coco, bool>> expr3 = UpdateParameter(expr2, expr1.Parameters[0]);
var expr4 = Expression.Lambda<Func<Recording, bool>>(
Expression.OrElse(expr1.Body, expr3.Body), expr1.Parameters[0]);
var func = expr4.Compile();
Assert.IsTrue(func(new Coco { Item1 = "caca", Item2 = "test pipi" }));
}
Solution 4
Nothing new here but married this answer with this answer and slightly refactored it so that even I understand what's going on:
public static class ExpressionExtensions
{
public static Expression<Func<T, bool>> AndAlso<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
{
ParameterExpression parameter1 = expr1.Parameters[0];
var visitor = new ReplaceParameterVisitor(expr2.Parameters[0], parameter1);
var body2WithParam1 = visitor.Visit(expr2.Body);
return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(expr1.Body, body2WithParam1), parameter1);
}
private class ReplaceParameterVisitor : ExpressionVisitor
{
private ParameterExpression _oldParameter;
private ParameterExpression _newParameter;
public ReplaceParameterVisitor(ParameterExpression oldParameter, ParameterExpression newParameter)
{
_oldParameter = oldParameter;
_newParameter = newParameter;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (ReferenceEquals(node, _oldParameter))
return _newParameter;
return base.VisitParameter(node);
}
}
}
Solution 5
I combined some beautiful answers here to make it possible to easily support more Expression operators.
This is based on the answer of @Dejan but now it's quite easy to add the OR as well. I chose not to make the Combine
function public, but you could do that to be even more flexible.
public static class ExpressionExtensions
{
public static Expression<Func<T, bool>> AndAlso<T>(this Expression<Func<T, bool>> leftExpression,
Expression<Func<T, bool>> rightExpression) =>
Combine(leftExpression, rightExpression, Expression.AndAlso);
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> leftExpression,
Expression<Func<T, bool>> rightExpression) =>
Combine(leftExpression, rightExpression, Expression.Or);
public static Expression<Func<T, bool>> Combine<T>(Expression<Func<T, bool>> leftExpression, Expression<Func<T, bool>> rightExpression, Func<Expression, Expression, BinaryExpression> combineOperator)
{
var leftParameter = leftExpression.Parameters[0];
var rightParameter = rightExpression.Parameters[0];
var visitor = new ReplaceParameterVisitor(rightParameter, leftParameter);
var leftBody = leftExpression.Body;
var rightBody = visitor.Visit(rightExpression.Body);
return Expression.Lambda<Func<T, bool>>(combineOperator(leftBody, rightBody), leftParameter);
}
private class ReplaceParameterVisitor : ExpressionVisitor
{
private readonly ParameterExpression _oldParameter;
private readonly ParameterExpression _newParameter;
public ReplaceParameterVisitor(ParameterExpression oldParameter, ParameterExpression newParameter)
{
_oldParameter = oldParameter;
_newParameter = newParameter;
}
protected override Expression VisitParameter(ParameterExpression node)
{
return ReferenceEquals(node, _oldParameter) ? _newParameter : base.VisitParameter(node);
}
}
}
Usage is not changed and still like this:
Expression<Func<Result, bool>> noFilterExpression = item => filters == null;
Expression<Func<Result, bool>> laptopFilterExpression = item => item.x == ...
Expression<Func<Result, bool>> dateFilterExpression = item => item.y == ...
var combinedFilterExpression = noFilterExpression.Or(laptopFilterExpression.AndAlso(dateFilterExpression));
efQuery.Where(combinedFilterExpression);
(This is an example based on my actual code, but read it as pseudo-code)
Related videos on Youtube
theh2o
Updated on March 05, 2022Comments
-
theh2o about 2 years
I have two expressions of type
Expression<Func<T, bool>>
and I want to take to OR, AND or NOT of these and get a new expression of the same typeExpression<Func<T, bool>> expr1; Expression<Func<T, bool>> expr2; ... //how to do this (the code below will obviously not work) Expression<Func<T, bool>> andExpression = expr AND expr2
-
andy almost 15 yearsHey Marc, I tried out your first suggestion, in the your first code block above, but when I pass in the "lambda" expression<func<T,bool>> result in a Where method, I get an error saying the parameter is out of scope? any idea? cheers
-
Marc Gravell almost 15 years@Andy - yes (see the first sentence) - you will get this if you are using different parameter instances in the two versions... I'll update with another option (but which doesn't work for all providers; LINQ-to-Objects and LINQ-to-SQL will be fine, but EF won't be...)
-
Marc Gravell almost 15 yearsD'oh! I've already covered this in the second version... it can be simplified a little bit, though (will update)
-
Nicolas Fall over 14 years+1 the generalized version works like a charm, I used And instead of andalso, I thought linq to sql doesn't support andalso?
-
Mikhaël Bois over 13 yearsyou are a legend. I just tried it and the generalised version works excellently! (I tested it with NH 3.0 alpha 2)
-
Nicolas Fall about 13 yearshmm getting the invoke not supported for EF4, is there a version that would work of yours for this marc?
-
Marc Gravell about 13 years@Maslow - here's a rewriter that can inline the trees to save Invoke: stackoverflow.com/questions/1717444/…
-
Nicolas Fall about 13 years@Marc - thanks a ton, I imagine there are other places in the code where this was needed, but it looks so far like just chaining .Where(cond1).FirstOrDefault(cond2) is working in the place that brought me back here, not sure about scalability or performance.
-
VulgarBinary over 11 yearsThis solution was the only one that allowed me to have x => x.Property == Value combined with arg => arg.Property2 == Value. Major props, a little terse and confusing but it works so I'm not going to complain. Kudos Adam :-)
-
Shaun Wilson about 11 yearsThis solved my particular problem where the other solution resulted in the same exception. Thanks.
-
Aaron Stainback almost 10 yearsThis is a great solution.
-
Aaron Stainback almost 10 yearsThis is a great solution.
-
Chris McAtackney almost 10 yearsAdam, this solved a very annoying problem I was having using the SharePoint Client Object model's Linq provider - thanks for posting it.
-
Aron over 9 years-1 for using
Expression.Invoke
instead of using an Expression Tree Visitor to substitute the parameters. -
Marc Gravell over 9 years@Aron now look at the date: the .NET framework visitor (
ExpressionVisitor
) did not exist back then; I have a related example on stackoverflow from a similar date where it implements the visitor manually: it is a lot of code. -
Aron over 9 years@MarcGravell ah...I didn't realise that the Visitor was a new thing for .net 4. Do you mind if I update your answer for new users?
-
Marc Gravell over 9 years@Aron by all means, go ahead
-
Prokurors about 9 years@Aron Hi, I tried Your update with
ExpressionVisitor
and I getExpression of type 'System.Func
2[(TSource),System.Boolean]' cannot be used for return type 'System.Boolean` what could be cause for that? -
Zain Shaikh over 8 yearsThanks @Marc, your solution helped a lot :) I needed this for
OrElse
condition so I modified your code for my needs. thanks again :) -
Romain Vergnory almost 8 yearsthis cannot be used in Linq to SQL for instance
-
Nishanth Shaan almost 8 yearshi @MarcGravell how to do it when we use two different 'ParameterExpression' please it would be really uselful for me
-
Marc Gravell almost 8 years@Nishanth just use a
Dictionary<Expression,Expression>
and someTryGetValue
and it should work for any number of swaps -
Nishanth Shaan almost 8 yearsThanks, @MarcGravell but, I have followed the tuto codeproject.com/Tips/582450/… But wat i need is to be able to query the child entities also.. is it possible? what I can do now is db.Jobs.Include(j => j.SubCategory).Where(MyExpressionBuilder.Build<Job>(filters)).ToList(); what i want to do : db.Jobs.Include(j => j.SubCategory). .Where(MyExpressionBuilder.Build<Job>(filters) && MyExpressionBuilder.Build<Subcategory>(filters2)).ToList(); plsConsultTheArticle forDetails, any idea please?
-
tokyo0709 over 7 yearsThis worked for me! I had searched for a variety of solutions as well as predicate builder and nothing worked until this. Thank you!
-
Alexander Derck over 7 years@MarcGravell Awesome piece of code! It's a bit faster to use expr1.Parameters[0] as the parameterexpression though, then you don't have to visit the left expression :-)
-
Tolga Evcimen about 7 yearsThis is a wonderful piece of code. I couldn't find a place to adjust the code, copy-paste and that's it :)
-
johnny 5 over 6 years@MarkGravell, I'm using your first solution to combine my expressions, and everything is working fine even in entityframework, So what would the benefits of using the last solution be?
-
Dave Cousineau over 6 years@johnny5 the first (simpler) solution should only work if the parameters within the lambda bodies are the same parameters. given
x => x > 5
andy => y < 100
you need to makez => z > 5 && z < 100
, notz => x > 5 && y < 100
. ifx
andy
are already the same parameter somehow, then that's no problem. if they aren't then one expression needs to be rewritten with the parameter of the other, (or both rewritten with a new one), which is what theReplaceExpressionVisitor
allows. (note that the parameters need to be the same objects not just have the same names). -
johnny 5 over 6 years@DaveCousineau Yeah I found this out that hard when when it came back to bite me. But just to add to that point event if you have
x => x > 5
and separate expressionx => x < 100
, those x's are local to the expression and if you try to use them even you will still get an exception -
Andreas over 5 yearsThank you very much
-
canton7 over 4 yearsNote that you can optimize this somewhat: you don't need to rewrite both
expr1
andexpr2
, but you can instead rewrite justexpr2
and replaceexpr2.Parameters[0]
withexpr1.Parameters[0]
, leavingexpr1
as-is -
Kevin M. Lapio about 4 yearsI was having difficulty grasping the concept, and your melding of a couple other answers helped it click for me. Thanks!
-
user3071284 almost 4 years@MarcGravell Thank you for this. Do you know if the first code block (2 lines) is EF Core safe? It's reporting at compile time that it's not translatable, but I can't see why.
-
Marc Gravell almost 4 years@user3071284 because parsing expression trees is hard, so sometimes we need to help them out; try the expression-visitor version at the bottom
-
ilyas varol about 2 yearsthanks, I integrated it to my code, but I didn't try if it works or not :)