Reputation: 53
I want to combile two expressions :
public Expression<Func<AnyType,bool>> BuildExpression(int id, string filtreA, string filtreB)
{
Expression<Func<AnyType, bool>> extraExpression = null;
Expression<Func<AnyType, bool>> expression = null;
expression = a => a.ID == id;
var fullExpression = expression.Body;
if (!String.IsNullOrEmpty(filtreA))
{
extraExpression = a => a.TYPE == filtreA;
fullExpression = Expression.AndAlso(fullExpression, extraExpression.Body);
}
if (!String.IsNullOrEmpty(filtreB))
{
extraExpression = a => a.TYPE == filtreB;
fullExpression = Expression.AndAlso(fullExpression, extraExpression.Body);
}
expression = Expression.Lambda<Func<AnyType, bool>>(fullExpression, expression.Parameters[0]);
}
return expression;
}
//I want my final expression to be a.ID == id && a.TYPE == filtreB for example
The problem is that I get the following error : System.InvalidOperationException. Le paramètre 'a' n'est pas dans la portée.. It only happens if enter in one of my ifs.
Does anyone know how I can handle this ? Thanks
ps: My question il similar to this post but it looks like the solution doesn't work anymore : Combining two expressions (Expression<Func<T, bool>>)
Upvotes: 0
Views: 2698
Reputation: 11100
Each parameter is a different ParameterExpression
instance. This problem would be more obvious if you had defined each expression with different parameter names;
expression = a => a.ID == id;
// ...
extraExpression = b => b.TYPE == filtreA;
// ...
extraExpression = c => c.TYPE == filtreB;
Note that entity framework core 2.1 and earlier didn't care if your parameters were different, and would seem to look through .Invoke
operations. But since version 3, with an internal rewrite of the expression compiler, neither of those are supported. Hence why earlier examples may not work anymore.
An expression visitor to swap parameter expressions isn't too complicated;
public class MapParameters : ExpressionVisitor
{
private readonly Dictionary<ParameterExpression, ParameterExpression> mapping;
public MapParameters(IEnumerable<ParameterExpression> before, IEnumerable<ParameterExpression> after)
{
this.mapping = new Dictionary<ParameterExpression, ParameterExpression>(
before
.Zip(after)
.Select(p => KeyValuePair.Create(p.First,p.Second))
);
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (mapping.TryGetValue(node, out var replace))
return replace;
return base.VisitParameter(node);
}
}
Expression.Lambda<Func<AnyType, bool>>(
Expression.AndAlso(
expression.Body,
new MapParameters(
extraExpression.Parameters,
expression.Parameters
).Visit(extraExpression.Body)
),
expression.Parameters);
Also note that if you are using this to build a single expression for IQueryable<T>.Where(...)
. You could consider calling .Where(...)
twice, which would achieve the same end result.
Upvotes: 1
Reputation: 27282
You have missed parameters replacement. It is a tricky with visitors. Trying to simplify your life, I would suggest to use popular library LINQKit and rewrite your expression building.
public Expression<Func<AnyType, bool>> BuildExpression(int id, string filtreA, string filtreB)
{
var predicate = PredicateBuilder.New<AnyType>(true);
predicate = predicate.And(a => a.ID == id);
if (!string.IsNullOrEmpty(filtreA))
{
predicate = predicate.And(a => a.TYPE == filtreA);
}
if (!string.IsNullOrEmpty(filtreB))
{
predicate = predicate.And(a => a.TYPE == filtreB);
}
return predicate;
}
Upvotes: 1