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>>)
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;
}
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.
Related
I'm creating Expression<Func<Entity, bool>> expression tree dynamically:
int id = ...;
Expression<Func<Entity, int>> keySelector = e => e.Id;
BinaryExpression equals = Expression.Equal(
keySelector.Body,
Expression.Constant(id, keySelector.Body.Type)); //replace this with a variable
var predicate = Expression.Lambda<Func<Entity, bool>>(equals, keySelector.Parameters[0]);
which produces equivalent of e => e.Id == 1.
How do I pass a variable instead of a constant: e => e.Id == id
You need to change the const to something that you can change a property of:
class IDHolder
{
public int Id { get; set; }
}
now you bind your expression to instance of IDHolder like so:
var idHolder = new IDHolder { Id = 0 };
Expression<Func<Entity, int>> keySelector = e => e.Id;
BinaryExpression equals = Expression.Equal(
keySelector.Body,
Expression.Property(
Expression.Constant(idHolder, typeof(IDHolder)),
nameof(IDHolder.Id))
);
var predicate = Expression.Lambda<Func<Entity, bool>>(equals, keySelector.Parameters[0]);
the predicate becomes slightly more complicated yet nothing that would make EF break:
x => x.Id == idHandler.Id
you can test this by compiling the predicate and running simple tests changing Id and the entity:
var func = predicate.Compile();
idHolder.Id = 5;
Console.WriteLine(func(new Entity { Id = 0 }).ToString()); // false
Console.WriteLine(func(new Entity { Id = 5 }).ToString()); // true
idHolder.Id = 6;
Console.WriteLine(func(new Entity { Id = 6 }).ToString()); // true
note that this approach is not thread safe and will result in strange behavior if you cache predicate and idHolder and use that in web api project or anything potentially using this predicate concurrently. To just auto translate some conditions from requests it would be better to just create the expression each time you need to use it in EF. There is no significant performance hit while creating expressions; the costly operation is the compilation and that is never done by EF.
Or maybe I misunderstood your question and you simply need a way to change the value of id, and as suggested by #Jochem Van Hespen you need a function:
public Expression<Func<Entity,bool>> GetPredicate(int id){
Expression<Func<Entity, int>> keySelector = e => e.Id;
BinaryExpression equals = Expression.Equal(
keySelector.Body,
Expression.Constant(id, keySelector.Body.Type));
var predicate = Expression.Lambda<Func<Entity, bool>>(equals, keySelector.Parameters[0]);
return predicate;
}
and you can use it like this:
_dbContext.Set<Entity>().Where(GetPredicate(4))
I have a list of conditions composed of two Funcs:
public Func<TConfiguration, string> ConfigurationField { get;}
public Func<TNumbering, string> NumberingField { get; }
For each condition, expression would look like this:
Expression<Func<TNumbering, TConfiguration, bool>> (n, c) => criteria.ConfigurationField(c) != criteria.NumberingField(n)
I need to chain the list of these expressions with OrElse.
I tried doing something like:
BinaryExpression expression = null;
foreach (var criteria in SelectionCriteria)
{
Expression<Func<TNumbering, TConfiguration, bool>> exp = (n, c) => criteria.ConfigurationField(c) != criteria.NumberingField(n);
expression = expression == null ? exp : Expression.OrElse(expression, exp);
}
if (expression == null) return Result.Failure("Expression not defined"));
var lambda = Expression.Lambda<Func<TConfiguration, bool>>(expression);
numberingsToRemove = numberings.Where(_ => configurations.All(lambda));
However, compiler doesn't like it, says there is no implicit conversion between Expression.Lambda<Func<TConfiguration, bool>> and Binary expression.
If I use
expression = expression == null ? Expression.OrElse(exp, exp) : Expression.OrElse(expression, exp);
I get
The binary operator OrElse is not defined for the types 'System.Func<TNumbering,TConfiguration,System.Boolean> and 'System.Func<TNumbering,TConfiguration,System.Boolean>.
I am new to building expressions, can somebody point me in the right direction how to do this?
Your Expression<Func<TNumbering, TConfiguration, bool>> is a generic type whose open generic type is Expression<TDelegate>, where TDelegate is some delegate type; in this case Func<TNumbering, TConfiguration, bool>.
Expression<TDelegate> inherits from LambdaExpression, which represents a C# (or VB.NET) lambda expression.
Just like you couldn't write the following code:
var result =
(n, c) => criteria.ConfigurationField(c) != criteria.NumberingField(n) ||
(n1, c1) => criteria.ConfigurationField(c1) != criteria.NumberingField(n1);
trying to combine two LambdaExpressions with OrElse would throw an exception at runtime.
Your code isn't even compiling, because expression is typed as BinaryExpression, meaning an expression corresponding to this:
criteria.ConfigurationField(c) != criteria.NumberingField(n) ||
criteria.ConfigurationField(c1) != criteria.NumberingField(n1)
into which you're trying to put a full Expression<TDelegate>, which includes (for example) the parameter list.
Every LambdaExpression has a Body property, which extracts from an expression corresponding to this:
(n, c) => criteria.ConfigurationField(c) != criteria.NumberingField(n)
the body of the LambdaExpression, or an expression corresponding to this:
criteria.ConfigurationField(c) != criteria.NumberingField(n)
which, in theory, you could then combine into a BinaryExpression corresponding to this:
criteria.ConfigurationField(c) != criteria.NumberingField(n) ||
criteria.ConfigurationField(c1) != criteria.NumberingField(n1)
But this wouldn't work either, because each iteration introduces multiple new parameters, all of which you'd have to pass in to the final lambda.
It's possible to work around this problem, but I would suggest first and foremost you map each element in SelectionCriteria to an expression corresponding to the criteria evaluation, using the factory methods at System.Linq.Expressions.Expression. You could then combine those expressions into a BinaryExpression which you could then wrap up in a LambdaExpression or even an Expression.
It might look something like this (making some assumptions):
class Criteria<TConfiguration, TNumbering> {
public Func<TConfiguration, string> ConfigurationField { get;}
public Func<TNumbering, string> NumberingField { get; }
}
// using static System.Linq.Expressions.Expression;
var SelectionCritera = new List<Criteria>();
/*
* populate list here
*/
var configParam = Parameter(typeof(TConfiguration));
var numberingParam = Parameter(typeof(TNumbering));
var expressions =
SelectionCriteria.Select(criteria => {
var criteriaExpr = Constant(criteria);
return NotEqual( // !=
Invoke( // ( ... )
PropertyOrField( // .ConfigurationField
criteriaExpr, // criteria
"ConfigurationField"
),
configParam // c
),
Invoke( // ( ... )
PropertyOrField( // .NumberingField
criteriaExpr, // criteria
"NumberingField"
),
numberingParam // n
)
);
})
.ToList();
if (!expressions.Any) { return Result.Failure("Expression not defined")); }
// Combine all the subexpressions using ||
var body = expressions.Aggregate((prev, next) => OrElse(prev, next));
// Create a LambdaExpression
var lmbd = Lambda<Func<TConfiguration, TNumbering, bool>>(body, configParam, numberingParam);
// Create a .NET method from the LambdaExpression
var mthd = lmbd.Compile();
// Apply the method to each config/numbering pair
var result = (
from config in configs
from numbering in numbering
select (config, numbering)
).All(x => mthd(config, numbering));
This question already has answers here:
Combining two expressions (Expression<Func<T, bool>>)
(10 answers)
Closed 9 months ago.
In the code below :
Expression<Func<WorkflowTask, bool>> filterBefore = wt => true;
filterBefore = filterBefore.And(wt => wt.code == "XK");
List<string> sourceLanguages = new List<string>() { "FR", "DE", "NL" };
//HOW TO BUILD OR CONDITIONS DYNAMICALLY BASED ON SOURCE LANGUAGES LIST ?
filterBefore = filterBefore.And(wt => wt.SourceLanguages.Contains("FR") || wt.WorkflowTaskContextualInfo.SourceLanguages.Contains("DE"));
I don't know how to build dynamically the OR condition on the SourceLanguages List. That list could contain any number of values (I've hardcoded it here for the sake of example).
wt.WorkflowTaskContextualInfo.SourceLanguages is a string with comma-separated values ("FR, EN" for instance)
The And expression is defined as below :
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
{
var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(expr1.Body, invokedExpr), expr1.Parameters);
}
LINQKit's PredicateBuilder is designed specifically to address this kind of need. But if you feel that's too much overhead, you can craft your own Expression tree with a few simple utilities, as I've described in this answer
First, a general-purpose Expression Replacer:
public class ExpressionReplacer : ExpressionVisitor
{
private readonly Func<Expression, Expression> replacer;
public ExpressionReplacer(Func<Expression, Expression> replacer)
{
this.replacer = replacer;
}
public override Expression Visit(Expression node)
{
return base.Visit(replacer(node));
}
}
Next, a simple utility method to replace one parameter's usage with another parameter in a given expression:
public static T ReplaceParameter<T>(T expr, ParameterExpression toReplace, ParameterExpression replacement)
where T : Expression
{
var replacer = new ExpressionReplacer(e => e == toReplace ? replacement : e);
return (T)replacer.Visit(expr);
}
This is necessary because the lambda parameters in two different expressions are actually different parameters, even when they have the same name. For example, if you want to end up with q => q.first.Contains(first) || q.last.Contains(last), then the q in q.last.Contains(last) must be the exact same q that's provided at the beginning of the lambda expression.
Next we need a general-purpose Join method that's capable of joining Func<T, TReturn>-style Lambda Expressions together with a given Binary Expression generator.
public static Expression<Func<T, TReturn>> Join<T, TReturn>(Func<Expression, Expression, BinaryExpression> joiner, IReadOnlyCollection<Expression<Func<T, TReturn>>> expressions)
{
if (!expressions.Any())
{
throw new ArgumentException("No expressions were provided");
}
var firstExpression = expressions.First();
var otherExpressions = expressions.Skip(1);
var firstParameter = firstExpression.Parameters.Single();
var otherExpressionsWithParameterReplaced = otherExpressions.Select(e => ReplaceParameter(e.Body, e.Parameters.Single(), firstParameter));
var bodies = new[] { firstExpression.Body }.Concat(otherExpressionsWithParameterReplaced);
var joinedBodies = bodies.Aggregate(joiner);
return Expression.Lambda<Func<T, TReturn>>(joinedBodies, firstParameter);
}
Now, applying that to your example:
Expression<Func<WorkflowTask, bool>> codeCriteria = wt => wt.code == "XK";
var langCriteria = new List<string>() { "FR", "DE", "NL" }
.Select(lang => (Expression<Func<WorkflowTask, bool>>)(wt => wt.SourceLanguages.Contains(lang)))
.ToList();
var filter = Join(Expression.And, new[] { codeCriteria, Join(Expression.Or, langCriteria)});
filter will now have the equivalent of wt => wt.code == "XK" && (wt.SourceLanguages.Contains("FR") || wt.SourceLanguages.Contains("DE") || wt.SourceLanguages.Contains("NL"))
I would put the required languages in an array or list.
var required = new string[]{ "FR", "DE" };
Then you can query with
wt => required.Any(r => wt.SourceLanguages.Contains(r))
or, the other way round
wt => wt.SourceLanguages.Any(sl => required.Contains(sl))
I did not feel like importing a whole library, and the sample given seemed a bit of a stretch so I think i found an easier solution using BinaryExpression Update(Expression left, LambdaExpression? conversion, Expression right).
The samples below accepts a list of string, and constructs a chain of OR expressions. Each OR expressions calls a entity framework's LIKE method. In the end the whole ordeal gets translated nicely to SQL - so if you're on the job of making dynamic filters like i did - this should help you out.
private static Expression<Func<TMeta, bool>> GetMetaKeyFilterPredicateExpression<TMeta>(List<string> metaKeyNames)
where TMeta : IMetaKeyValuePair
{
var parameter = Expression.Parameter(typeof(TMeta));
var property = Expression.Property(Expression.Convert(parameter, typeof(IMetaKeyValuePair)),
propertyName: nameof(IMetaKeyValuePair.MetaKey));
Expression body = null!;
BinaryExpression? predicateExpression = null;
foreach (var metaKeyName in metaKeyNames)
{
var likeExpression = Expression.Call(typeof(DbFunctionsExtensions),
nameof(DbFunctionsExtensions.Like),
null,
Expression.Constant(EF.Functions),
property,
Expression.Constant(metaKeyName)
);
predicateExpression =
predicateExpression == null
? Expression.Or(likeExpression, Expression.Constant(false))
: predicateExpression.Update(predicateExpression, null, likeExpression);
}
body = (Expression?)predicateExpression ?? Expression.Constant(true);
var expr = Expression.Lambda<Func<TMeta, bool>>(body: body, parameter);
return expr;
}
var expr = GetMetaKeyFilterPredicateExpression<TMeta>(metaKeyNames);
qMetaKeyValuePairs = qMetaKeyValuePairs.Where(expr);
I've been troubleshooting an unusual issue with some EF 6 code where queries running with the Oracle.ManagedDataAccess.Client driver are sometimes taking several minutes to return results even when the underlying query executes within 2ms. An example query would be as follows:
var result = users.Where(u => u.username == varUserName).FirstOrDefault();
This query might take several minutes to return, however if I replace the query with the same thing with a constant in the lambda function, it runs instantaneously:
var result = users.Where(u => u.username == "testUsername").FirstOrDefault();
To work around this issue, I can either write parameterised SQL queries, or I can manually generate an appropriate lambda expression tree:
var userParam = Expression.Parameter(typeof(Entity.User), "user");
var userNameField = Expression.Property(userParam, "username");
var userNameConstant = Expression.Constant(varUserName, typeof(string));
var equalUserName = Expression.Equal(userNameField, userNameConstant);
var lambda = Expression.Lambda<Func<Entity.User, bool>>(equalUserName, new ParameterExpression[] { userParam });
var result = users.Where(lambda).FirstOrDefault();
Because this works, it begs the question: is there a way to easily generate lambda expression trees which result in variables being directly included as constants, instead of references to variables?
For example, something like this would be ideal:
var lambdaExpression = (u => u.username == varUserName).ReplaceVariablesWithConstants();
It can be done relatively easy with ExpressionVisitor which evaluates the ConstantExpression members like this:
public static class ExpressionUtils
{
public static Expression<TDelegate> ReplaceVariablesWithConstants<TDelegate>(this Expression<TDelegate> source)
{
return source.Update(
new ReplaceVariablesWithConstantsVisitor().Visit(source.Body),
source.Parameters);
}
class ReplaceVariablesWithConstantsVisitor : ExpressionVisitor
{
protected override Expression VisitMember(MemberExpression node)
{
var expression = Visit(node.Expression);
if (expression is ConstantExpression)
{
var variable = ((ConstantExpression)expression).Value;
var value = node.Member is FieldInfo ?
((FieldInfo)node.Member).GetValue(variable) :
((PropertyInfo)node.Member).GetValue(variable);
return Expression.Constant(value, node.Type);
}
return node.Update(expression);
}
}
}
It's kinda hard. You need to modify the ExpressionsTree with a ExpressionsVisitor. It would probably become something like:
var lambdaExpression = ReplaceVariablesWithConstants(u => u.username == varUserName);
So I had written this LINQ query using reflection, and later found out it isn't supported. What would be the best way to get the same functionality from this code?
List<Profile> profilesFromUUID = await MobileService.GetTable<Profile>().Where(p => typeof(Profile)
.GetProperty(handler.Name + "UUID").GetValue(p) == obj.uuid).ToListAsync();
Use the reflection to create the query, not in the query. Consider:
public static IQueryable<Profile> Filter(
this IQueryable<Profile> source, string name, Guid uuid)
{
// .<name>UUID
var property = typeof(Profile).GetProperty(name + "UUID");
// p
var parExp = Expression.Parameter(typeof(Profile));
// p.<name>UUID
var methodExp = Expression.Property(parExp, property);
// uuid
var constExp = Expression.Constant(uuid, typeof(Guid));
// p.<name>UUID == uuid
var binExp = Expression.Equal(methodExp, constExp);
// p => p.<name>UUID == uuid
var lambda = Expression.Lambda<Func<Profile, bool>>(binExp, parExp);
// source.Where(p => p.<name>UUID == uuid)
return source.Where(lambda);
}
This builds up the expression first (so if name was "Test" it would create the expression corresponding with p => p.TestUUID == uuid and then uses that in the call to Where.
Because this step is done first, rather than within the expression itself, there's no need for the query engine to try to translate typeof or GetProperty() into SQL (which it of course, couldn't do).
So:
var filtered = MobileService.GetTable<Profile>().Filter(handler.Name, obj.uuid);
Returns an IQueryable<Profile> with the appropriate Where attached. And so:
var profilesFromUUID = await MobileService.GetTable<Profile>().Filter(handler.Name, obj.uuid).ToListAsync();
Will as a whole first use reflection to build the query, then apply the query, then produce a list from it asynchrously and then wait for its results.
It's worth noting that since Filter() will accept any IQueryable<Profile> they can be either chained or unioned. So:
MobileService.GetTable<Profile>().Filter("A", uuid0).Filter("B", uuid1);
Is equivalent to:
from p in MobileService.GetTable<Profile>() where p.AUUID = uuid0 && p.BUUID == uuid1
And:
MobileService.GetTable<Profile>().Filter("A", uuid0).Union(
MobileService.GetTable<Profile>().Filter("B", uuid1))
Is equivalent to:
from p in MobileService.GetTable<Profile>() where p.AUUID = uuid0 || p.BUUID == uuid1
A more generalised version would be:
public static IQueryable<TSource> FilterByNamedProperty<TSource, TValue>(this IQueryable<TSource> source, string propertyName, TValue value)
{
var property = typeof(TSource).GetProperty(propertyName);
var parExp = Expression.Parameter(typeof(TSource));
var methodExp = Expression.Property(parExp, property);
var constExp = Expression.Constant(value, typeof(TValue));
var binExp = Expression.Equal(methodExp, constExp);
var lambda = Expression.Lambda<Func<TSource, bool>>(binExp, parExp);
return source.Where(lambda);
}
Then while you have to do the + "UUID" in the calling code, you can use this to do analogous queries with any IQueryable<> of any element type.
How about just compare all property name? By definition UUID would not have collision anyway. Since Profile is just a data class, the # of the property for UUID is fixed.
List<Profile> profilesFromUUID = await MobileService.GetTable<Profile>()
.Where(p =>
p.A_UUID == obj.uuid ||
p.B_UUID == obj.uuid ||
p.C_UUID == obj.uuid)
.ToListAsync();
Or add a method (extension method) for Profile like:
public static Guid GetUUIDByTableName(this Profile value, string tableName)
{
switch (tableName)
{
case "A_": return value.A_UUID;
case "B_": return value.B_UUID;
default: return Guid.Empty;
}
}
List<Profile> profilesFromUUID = await MobileService.GetTable<Profile>()
.Where(p => p.GetUUIDByTableName(handler.Name) == obj.uuid)
.ToListAsync();