If I have a specification defined as an Expression as below:
public Expression<Func<Foo, bool>> IsSuperhuman =
x => x.CanFly && x.HasXRayVision;
And I want to define another specification 'IsSuperheroine' with the logic 'is superhuman and is female', how can I reuse the existing specification within the new one?
Have you checked out predicate builder in LinqKit? It builds up expressions by letting you and and or expressions together.
Here's a way to do it :
Expression<Func<Foo, bool>> IsSuperhuman = x => x.CanFly && x.HasXRayVision;
Expression<Func<Foo, bool>> IsSuperheroine = AndAlso(IsSuperhuman, x => x.IsFemale);
...
public static Expression<Func<T, TResult>> AndAlso<T, TResult>(Expression<Func<T, TResult>> expr1, Expression<Func<T, TResult>> expr2)
{
var arg = Expression.Parameter(typeof(T), expr1.Parameters[0].Name);
var andExpr = Expression.AndAlso(
ReplaceParameter(expr1.Body, expr1.Parameters[0], arg),
ReplaceParameter(expr2.Body, expr2.Parameters[0], arg));
return Expression.Lambda<Func<T, TResult>>(andExpr, arg);
}
public static Expression ReplaceParameter(Expression expr, ParameterExpression oldParam, ParameterExpression newParam)
{
return new ReplaceParameterVisitor(oldParam, newParam).Visit(expr);
}
internal class ReplaceParameterVisitor : ExpressionVisitor
{
private ParameterExpression _oldParam;
private ParameterExpression _newParam;
public ReplaceParameterVisitor(ParameterExpression oldParam, ParameterExpression newParam)
{
_oldParam = oldParam;
_newParam = newParam;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (node == _oldParam)
return _newParam;
return node;
}
}
It is probably not the simplest way to do it, but it works...
Related
This approach of including sub predicate in the base expression works:
private void SomeCallerMethod()
{
var predicate = GetBasePredicate(filterA:"123456");
}
private Expression<Func<Item, bool>> GetBasePredicate(string filterA)
{
Expression<Func<PublicationItem, bool>> predicate = PublicationPredicateExtensions.GetRootExpression();
if (!string.IsNullOrEmpty(filterA)
predicate = predicate.And(PublicationPredicateExtensions.Get_FilterA_Expression(filterA));
return predicate;
}
public static Expression<Func<Item, bool>> Get_FilterA_Expression(string filterA)
{
return (p => !p.Meta.Contains(filterA));
}
Building predicate collection by adding the sub predicate to the base predicate does not work:
basePredicate = GetBasePredicate();
basePredicate.And(p => !p.Meta.Contains("123456"));
private Expression<Func<Item, bool>> GetBasePredicate()
{
Expression<Func<Item, bool>> predicate = PublicationPredicateExtensions.GetRootExpression();
return predicate;
}
When combining expression trees, you can't just use And(p => !p.Meta.Contains("123456") (or even AndAlso) - you need to resolve that second expression in terms of the same parameters used by the first. One way to do this is by using Expression.Invoke to call the inner lambda by passing in the parameter of the outer, however: this is not universally supported by all LINQ implementations. The more useful approach is to rewrite the second query completely (swapping all the p for whatever the original parameter expression was), and use the body directly; like:
Expression<Func<string, bool>> a = s => s.StartsWith("a");
Expression<Func<string, bool>> b = t => t.EndsWith("b");
var both = AndAlso(a, b);
Console.WriteLine(both); // s => (s.StartsWith("a") AndAlso s.EndsWith("b"))
// ...
static Expression<Func<T, bool>> AndAlso<T>(Expression<Func<T, bool>> x, Expression<Func<T, bool>> y)
{
// GIGO
if (x is null) return y;
if (y is null) return x;
// combine, swapping y's parameter if it doesn't match x's
ParameterExpression xp = x.Parameters.Single(), yp = y.Parameters.Single();
var yBody = ReferenceEquals(xp, yp) ? y.Body : new SwapVisitor(yp, xp).Visit(y.Body);
return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(x.Body, yBody), x.Parameters);
}
class SwapVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public SwapVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
=> ReferenceEquals(node, from) ? to : base.Visit(node);
}
Is that possible to convert Expression to Expression<Func<T, bool>> if instance of Expression was created on T ?
At the end I have list List<Expression> and need to produce on Expression<Func<T, bool>> where each expression of List<Expression> is agregated with AND.
Yes; just call Expression.Lambda<Func<T, bool>>(..., parameter), where ... is an expression composed of the expressions you want to combine.
You'd probably want list.Aggregate(Expressions.AndAlso).
If your expressions don't all share the same ParameterExpression, you'll need to rewrite them to do so. (use ExpressionVisitor)
It's possible, but every expression in the list must actually be a Expression<Func<T, bool>> instance.
EDIT: It turns out that you use Kendo.Mvc.IFilterDescriptor.CreateFilterExpression which actually builds a MethodCallExpressions.
The following helper method should do the job (works with both lambda and method call expressions):
public static class Utils
{
public static Expression<Func<T, bool>> And<T>(List<Expression> expressions)
{
var item = Expression.Parameter(typeof(T), "item");
var body = expressions[0].GetPredicateExpression(item);
for (int i = 1; i < expressions.Count; i++)
body = Expression.AndAlso(body, expressions[i].GetPredicateExpression(item));
return Expression.Lambda<Func<T, bool>>(body, item);
}
static Expression GetPredicateExpression(this Expression target, ParameterExpression parameter)
{
var lambda = target as LambdaExpression;
var body = lambda != null ? lambda.Body : target;
return new ParameterBinder { value = parameter }.Visit(body);
}
class ParameterBinder : ExpressionVisitor
{
public ParameterExpression value;
protected override Expression VisitParameter(ParameterExpression node)
{
return node.Type == value.Type ? value : base.VisitParameter(node);
}
}
}
I want a function Expression> AnyColumnContains(string[] value)
that iterates through all Columns of a table and checks an array of values against the columns and returns true only if every value is contained in any column.
i already have a function that matches every column against one value but i have problems extending it to check the columns against every value
This is what i've got:
Expression<Func<T, bool>> AnyColumnContains<T>(string value){
var p = Expression.Parameter(typeof(T), "entity");
var fieldAccessors = typeof(T)
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(f => f.PropertyType == typeof(string))
.Select(f => Expression.Property(p, f))
.ToArray();
var fieldArray = Expression.NewArrayInit(typeof(string), fieldAccessors);
var concatCall = Expression.Call(typeof(string).GetMethod(
"Concat", new[] { typeof(string[]) }), fieldArray);
var contains = Expression.Call(
concatCall,
typeof(string).GetMethod("Contains", new[] { typeof(string) }),
Expression.Constant(value));
return Expression.Lambda<Func<T, bool>>(contains, p);
}
I tried to use a own extension method and replaced Contains with it but the problem is that i use sqlite and the expression cannot be converted since the Provider doesn't know the methods
This is what i want:
Expression<Func<T, bool>> AnyColumnContains<T>(string[] values){
// ... //
var contains = // build Expression Tree that matches all values against concatCall and only returns true if all values are contained.
return Expression.Lambda<Func<T, bool>>(contains, p);
}
Rather than making an entirely new method from scratch, you can simply compose the method that you already have that's working.
We can use the following method to combine predicates together:
public static class PredicateBuilder
{
public static Expression<Func<T, bool>> True<T>() { return f => true; }
public static Expression<Func<T, bool>> False<T>() { return f => false; }
public static Expression<Func<T, bool>> Or<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var secondBody = expr2.Body.Replace(expr2.Parameters[0], expr1.Parameters[0]);
return Expression.Lambda<Func<T, bool>>
(Expression.OrElse(expr1.Body, secondBody), expr1.Parameters);
}
public static Expression<Func<T, bool>> And<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var secondBody = expr2.Body.Replace(expr2.Parameters[0], expr1.Parameters[0]);
return Expression.Lambda<Func<T, bool>>
(Expression.AndAlso(expr1.Body, secondBody), expr1.Parameters);
}
}
It relies on the following method to replace all instance of one expression with another:
public static Expression Replace(this Expression expression,
Expression searchEx, Expression replaceEx)
{
return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
internal class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public ReplaceVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
Now all we have to do is call the single value version of AnyColumnContains for each value and Or all of the results together:
public static Expression<Func<T, bool>> AnyColumnContains<T>(IEnumerable<string> values)
{
return values.Select(value => AnyColumnContains<T>(value))
.Aggregate((a, b) => a.Or(b));
}
all
I try to build a dynamic linq query by expression tree, below is my code
Expression<Func<User, bool>> filter = c => c.isAdmin == false;
Expression<Func<User, bool>> filterForExistUser = c => (c.isfreezed == null ? false : c.isfreezed) != true;
Expression<Func<User, bool>> finalFilter = Expression.Lambda<Func<User, bool>>(Expression.AndAlso(filter.Body, filterForExistUser.Body), filter.Parameters);
IQueryable<User> myusers = db.Users.AsQueryable<User>();
MethodCallExpression whereCallExpression = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { myusers.ElementType },
myusers.Expression,
finalFilter);
IQueryable<User> results = myusers.Provider.CreateQuery<User>(whereCallExpression);
foreach (User user in results)
Console.WriteLine(user.UserName);
But system report "unding parameter c" error, how I can fix it?
Thanks
Although the parameters in the filter and filterForExistUser expressions share the same name, they both are referring to different symbols from different scopes. Your new expression uses the parameter from the filter expression, but you left out the parameter for the filterForExistUser. You must rewrite the expression so the same single parameter is used throughout the overall expression.
Here's a general purpose method you can use to combine the predicates:
Expression<Func<TSource, bool>> CombinePredicates<TSource>(
Expression<Func<TSource, bool>> head,
params Expression<Func<TSource, bool>>[] tail)
{
var param = head.Parameters.Single();
var body = tail.Aggregate(
head.Body,
(result, expr) => Expression.AndAlso(result,
new SubstitutionVisitor
{
OldExpr = expr.Parameters.Single(),
NewExpr = param,
}.Visit(expr.Body)
)
);
return Expression.Lambda<Func<TSource, bool>>(body, param);
}
public class SubstitutionVisitor : ExpressionVisitor
{
public Expression OldExpr { get; set; }
public Expression NewExpr { get; set; }
public override Expression Visit(Expression node)
{
return (node == OldExpr) ? NewExpr : base.Visit(node);
}
}
The key is the SubstitutionVisitor which is used to substitute the "tail" paramters using the "head" parameter.
So your final filter becomes this:
Expression<Func<User, bool>> finalFilter =
CombinePredicates(filter, filterForExistUser);
Forgive me, I'm not entirely sure my question is worded correctly.
I'm creating a search component where the user can search different fields with different operators... e.g. description.contains(keywords) and measurement.startsWith(yards).....
So here is what I have:
void SearchDescription(IQueryable<MyClass> results, string keywords)
{
switch(operator)
{
case "Contains":
results=results.Where(ele => ele.description.Contains(keywords));
break;
case "StartsWith":
results = results.Where(ele => ele.description.StartsWith(keywords));
break;
... and so on.....
}
}
Currently I have a method just as above for each field.... SearchDescription(), SearchID(), SearchMeasure(), etc. The only difference being the field/property name.
UPDATE
Upon further research possibly something like:
void Search(IQueryable<Entity> results, string keywords, Expression<Func<Entity>,object>> predicate)
{
results = results.Where(ele => predicate.Contains(keywords));
}
which could be called like:
Search(results, "my search terms", ele => ele.description);
This obviously doesn't work in it's current form, but maybe that is a clearer description of what I am after.
Thanks for all the responses so far.
This can be done by implementing a Compose method that will take two expressions and return an expression that acts as if it would invoke the first, then provide that as the parameter to the second:
void Search(IQueryable<Entity> results, string keywords,
Expression<Func<Entity, string>> selector)
{
results = results.Where(selector.Compose(obj => obj.Contains(keywords)));
}
To implement that we'll start off with a helper method that allows us to replace all instances of one expression with another:
internal class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public ReplaceVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
public static Expression Replace(this Expression expression,
Expression searchEx, Expression replaceEx)
{
return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
Using that tool it's as simple as a handful of replacements stuffed back together into a lambda:
public static Expression<Func<TFirstParam, TResult>>
Compose<TFirstParam, TIntermediate, TResult>(
this Expression<Func<TFirstParam, TIntermediate>> first,
Expression<Func<TIntermediate, TResult>> second)
{
var param = Expression.Parameter(typeof(TFirstParam), "param");
var newFirst = first.Body.Replace(first.Parameters[0], param);
var newSecond = second.Body.Replace(second.Parameters[0], newFirst);
return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}
It also seems odd to filter the query to items where a single string with all of the words is contained in the given field. It seems more likely that you want to get items that contain any of a list of strings. That's different, and requires just a touch more work.
We can use a new class we'll call a PredicateBuilder to build up a filter that takes the logical OR of a bunch of other filters.
void Search(IQueryable<Entity> results, IEnumerable<string> keywords,
Expression<Func<Entity, string>> selector)
{
var finalFilter = keywords.Aggregate(
PredicateBuilder.False<Entity>(),
(filter, keyword) => filter.Or(
selector.Compose(obj => obj.Contains(keyword))));
results = results.Where(finalFilter);
}
We can implement this class using the Replace method defined earlier like so:
public static class PredicateBuilder
{
public static Expression<Func<T, bool>> True<T>() { return f => true; }
public static Expression<Func<T, bool>> False<T>() { return f => false; }
public static Expression<Func<T, bool>> Or<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var secondBody = expr2.Body.Replace(expr2.Parameters[0], expr1.Parameters[0]);
return Expression.Lambda<Func<T, bool>>
(Expression.OrElse(expr1.Body, secondBody), expr1.Parameters);
}
public static Expression<Func<T, bool>> And<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var secondBody = expr2.Body.Replace(expr2.Parameters[0], expr1.Parameters[0]);
return Expression.Lambda<Func<T, bool>>
(Expression.AndAlso(expr1.Body, secondBody), expr1.Parameters);
}
}
You can use System.Reflection to retrieve a PropertyInfo using the name of the wanted property. Note that Reflection is a bit slow and if used many times in a second, it can seriously slow your program down.
void Search(IQueryable<MyClass> results, string keywords, string propertyName)
{
PropertyInfo elePropInfo = ele.GetType().GetProperty(propertyName);
string elePropValue = (string)elePropInfo.GetValue(ele, null); // the second argument should be null for non-indexed properties
switch(operator)
{
case "Contains":
results = results.Where(ele => elePropValue.Contains(keywords));
break;
case "StartsWith":
results = results.Where(ele => elePropValue.StartsWith(keywords));
break;
// etc
}
}
More info on GetProperty() can be found here in MSDN: http://msdn.microsoft.com/en-us/library/kz0a8sxy(v=vs.110).aspx