I'm using net5.0 and EntityFrameworkCore 5.0.4.
I have a search method that has optional strings to search for on a DataContext in EFCore.
I want to check if each of the strings is not null or white space.
I could do this:
var query = context.Model.AsQueryable();
if (!string.IsNullOrWhiteSpace(parameters.Id))
{
query = query.Where(x => x.Id.ToLower().Contains(parameters.Id.ToLower()));
}
but that is just horrible to maintain. What I started trying to get going is this:
public static IQueryable<T> FilterBy<T>(this IQueryable<T> query, string? searchValue,
Expression<Func<T, string>> getValueExpression)
{
if (string.IsNullOrWhiteSpace(searchValue?.Trim()))
{
return query;
}
// return with a where clause
}
I got this working but it does not support accessing joins that I need and I get the feeling this is not a good way:
var searchLower = searchValue.Trim().ToLower();
var propertyName = getValueExpression.GetMemberAccess().Name;
return query.Where(x => EF.Property<string?>(x!, propertyName)!.ToLower().Contains(searchLower));
I want to use it like so:
query
.FilterBy(parameters.Id, x => x.Id)
.FilterBy(parameters.Name, x => x.Name)
.FilterBy(parameters.CompanyName, x => x.Company.Name) // access via include/join
Solution
private static readonly MethodInfo StringContainsMethod =
typeof(string).GetMethod(nameof(string.Contains), new[] {typeof(string)})!;
private static readonly MethodInfo StringToLowerMethod =
typeof(string).GetMethod(nameof(string.ToLower), Type.EmptyTypes)!;
public static IQueryable<T> FilterBy<T>(this IQueryable<T> query, string? searchValue,
Expression<Func<T, string?>> memberExpression)
{
if (string.IsNullOrWhiteSpace(searchValue))
return query;
var valueExpression = Expression.Constant(searchValue.ToLower());
var toLower = Expression.Call(memberExpression.Body, StringToLowerMethod);
var call = Expression.Call(toLower, StringContainsMethod, valueExpression);
var sourceParam = memberExpression.Parameters.First();
Expression<Func<T, bool>> predicate = Expression.Lambda<Func<T, bool>>(call, sourceParam);
return query.Where(predicate);
}
Integer
public static IQueryable<T> FilterBy<T>(this IQueryable<T> query, int? filterInteger,
Expression<Func<T, int?>> memberExpression)
{
if (filterInteger == null)
return query;
var valueExpression = Expression.Constant(filterInteger);
var call = Expression.Equal(memberExpression.Body, valueExpression);
var sourceParam = memberExpression.Parameters.First();
Expression<Func<T, bool>> predicate = Expression.Lambda<Func<T, bool>>(call, sourceParam);
return query.Where(predicate);
}
Boolean
public static IQueryable<T> FilterBy<T>(this IQueryable<T> query, bool? filterBoolean,
Expression<Func<T, bool?>> memberExpression)
{
if (filterBoolean == null)
return query;
var valueExpression = Expression.Constant(filterBoolean);
var call = Expression.Equal(memberExpression.Body, valueExpression);
var sourceParam = memberExpression.Parameters.First();
Expression<Func<T, bool>> predicate = Expression.Lambda<Func<T, bool>>(call, sourceParam);
return query.Where(predicate);
}
I've not tested this but I believe you'd need to use expressions to achieve this. The following will build an expression to use as the predicate in the Where method:
public static IQueryable<T> FilterBy<T>(
this IQueryable<T> query,
string searchValue,
Expression<Func<T, string>> memberExpression)
{
if (string.IsNullOrWhiteSpace(searchValue))
return query;
// must be a lambda expression
LambdaExpression lambdaExpression = memberExpression as LambdaExpression;
if (lambdaExpression == null)
throw new ArgumentException($"Expression '{memberExpression}' is not a lambda expression.");
// get the member
Func<ParameterExpression, Expression> sourceExpression = source => Expression.Invoke(lambdaExpression, source);
ParameterExpression sourceParameter = Expression.Parameter(typeof(T), "source");
Expression sourceMember = sourceExpression(sourceParameter);
// expression for the search value
ConstantExpression valueExpression = Expression.Constant(searchValue);
// expression to call the Contains method
MethodInfo containsMethod = GetContainsMethod();
MethodCallExpression callExpression = Expression.Call(null, containsMethod, sourceMember, valueExpression);
// predicate expression
Expression<Func<T, bool>> predicate = Expression.Lambda<Func<T, bool>>(callExpression, sourceParameter);
return query.Where(predicate);
}
private static MethodInfo GetContainsMethod()
{
// get method
MethodInfo genericContainsMethod = typeof(Queryable)
.GetMethods(BindingFlags.Static | BindingFlags.Public)
.First(m => m.Name == "Contains"
&& m.IsGenericMethod
&& m.GetParameters().Count() == 2);
// apply generic types
MethodInfo containsMethod = genericContainsMethod
.MakeGenericMethod(new Type[] { typeof(string) });
return containsMethod;
}
Related
I want to implement custom .Contains() method with the following syntax:
static IQueryable<T> IsContainedBy<T, K>(this IQueryable<T> source, List<K> items, Expression<Func<T, K>> exp)
This method should look for coincidence in items array. Expression<Func<T, K>> exp provides property of object to be used for comparison. I have written the following code:
private IQueryable<T> WhereIsContainedBy<T, K>(IQueryable<T> source, List<K> items, Expression<Func<T, K>> exp) {
if (items == null || items.Count == 0)
return source;
MethodInfo containsMethod = typeof(List<K>).GetMethod("Contains", new[] { typeof(K) });
MethodCallExpression a =
Expression.Call(Expression.Constant(items), containsMethod, new Expression[] { exp.Body
/* Expression.Constant("31212eb5-cd5d-4f77-858a-a7ddba8e3d2c")*/ });
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(a, Expression.Parameter(typeof(T)));
return source.Where(lambda);
}
But i get the following error:
var source = new List<string>() { "31212eb5-cd5d-4f77-858a-a7ddba8e3d2c" };
var r = WhereIsContainedBy(_context.StageActions, source, a => a.StageActionId);
var a = await r.ToListAsync();
---
Where(s => List<string> { "31212eb5-cd5d-4f77-858a-a7ddba8e3d2c", }.Contains(a.StageActionId))' could not be translated.
But if I use commented Expression.Constant..... it works just fine.
Try reusing parameter from selector expression (i.e. exp) instead of creating a new one for resulting expression :
Expression<Func<T, bool>> lambda =
Expression.Lambda<Func<T, bool>>(a, exp.Parameters[0]);
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));
}
I've looked into many generic linq filtering questions and their answers here in SO but none of them satisfy my needs so I thought I should create a question.
I've created many of what I call "filter provider" classes, one for each entity class in my model, to provide a simplistic search for my application. I didn't want to go into more advanced solutions like Lucene.Net because a basic filtering with matching score would suffice.
Inside each one of these provider classes there are multiple methods that will receive the filtering terms and query specific properties, returning a score for each match based on the relevance of the property. Most methods will filter multiple properties at once, but not all.
Here are two of these methods:
private IQueryable<Retailer> MatchHighRelevanceFields(string searchTerm, IQueryable<Retailer> retailers)
{
var results = retailers.Where(r =>
(r.CompanyName != null && r.CompanyName.ToUpper().Contains(searchTerm))
|| (r.TradingName != null && r.TradingName.ToUpper().Contains(searchTerm))
);
return results;
}
private IQueryable<Retailer> MatchMediumRelevanceFields(string searchTerm, IQueryable<Retailer> retailers)
{
var results = retailers.Where(r =>
(r.Address.Street != null && r.Address.Street.ToUpper().Contains(searchTerm))
|| (r.Address.Complement != null && r.Address.Complement.ToUpper().Contains(searchTerm))
);
return results;
}
These methods are replicated ad nauseum throughout each provider class and I hope I could replace them for a single method that would receive the properties to be included in the query.
Something like:
public static IQueryable<T> Match<T>(string searchTerm, IQueryable<T> data, Expression<Func<T, string>> filterProperties)
{
var results = **build the query for each property in filterProperties**
return results;
}
But I really can't figure it out. I tried using reflection but it only worked with Linq to Objects and I need a solution for Linq to Entities.
So to solve this problem we need a few puzzle pieces first. The first puzzle piece is a method that can take an expression that computes a value, and then another expression that computes a new value taking the same type the first returns, and creates a new expression that represents the result of passing the result of the first function as the parameter to the second. This allows us to Compose expressions:
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);
}
This relies on the following tool to replace all instances 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);
}
}
We'll also need a tool to help us OR two predicate expressions 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);
}
}
Now that we have this we can use Compose on each property selector to map it from the property results to whether or not that property value is non-null and contains the search term. We can then OR all of those predicates together to get a filter for your query:
public static IQueryable<T> Match<T>(
IQueryable<T> data,
string searchTerm,
IEnumerable<Expression<Func<T, string>>> filterProperties)
{
var predicates = filterProperties.Select(selector =>
selector.Compose(value =>
value != null && value.Contains(searchTerm)));
var filter = predicates.Aggregate(
PredicateBuilder.False<T>(),
(aggregate, next) => aggregate.Or(next));
return data.Where(filter);
}
You can do it with expression trees but it's not as simple as you might think.
public static IQueryable<T> Match<T>(this IQueryable<T> data, string searchTerm,
params Expression<Func<T, string>>[] filterProperties)
{
var parameter = Expression.Parameter(typeof (T), "source");
Expression body = null;
foreach (var prop in filterProperties)
{
// need to replace all the expressions with the one parameter (gist taken from Colin Meek blog see link on top of class)
//prop.body should be the member expression
var propValue =
prop.Body.ReplaceParameters(new Dictionary<ParameterExpression, ParameterExpression>()
{
{prop.Parameters[0], parameter}
});
// is null check
var isNull = Expression.NotEqual(propValue, Expression.Constant(null, typeof(string)));
// create a tuple so EF will parameterize the sql call
var searchTuple = Tuple.Create(searchTerm);
var matchTerm = Expression.Property(Expression.Constant(searchTuple), "Item1");
// call ToUpper
var toUpper = Expression.Call(propValue, "ToUpper", null);
// Call contains on the ToUpper
var contains = Expression.Call(toUpper, "Contains", null, matchTerm);
// And not null and contains
var and = Expression.AndAlso(isNull, contains);
// or in any additional properties
body = body == null ? and : Expression.OrElse(body, and);
}
if (body != null)
{
var where = Expression.Call(typeof (Queryable), "Where", new[] {typeof (T)}, data.Expression,
Expression.Lambda<Func<T, bool>>(body, parameter));
return data.Provider.CreateQuery<T>(where);
}
return data;
}
public static Expression ReplaceParameters(this Expression exp, IDictionary<ParameterExpression, ParameterExpression> map)
{
return new ParameterRebinder(map).Visit(exp);
}
Now you need to have a expressionvisitor to make all the expressions use one parameter
//http://blogs.msdn.com/b/meek/archive/2008/05/02/linq-to-entities-combining-predicates.aspx
public class ParameterRebinder : ExpressionVisitor
{
private readonly IDictionary<ParameterExpression, ParameterExpression> _map;
public ParameterRebinder(IDictionary<ParameterExpression, ParameterExpression> map)
{
_map = map;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (_map.ContainsKey(node))
{
return _map[node];
}
return base.VisitParameter(node);
}
}
Would use it like
var matches = retailers.Match("7", r => r.Address.Street, x => x.Address.Complement).ToList();
Warning - I checked this with linq to objects using the AsQueryable but didn't run it against EF.
You can use Linq.Dynamic to build the query.
public static IQueryable<T> Match<T>(
string searchTerm,
IQueryable<T> data,
params Expression<Func<T, string>>[] filterProperties) where T : class
{
var predicates = new List<string>();
foreach (var prop in filterProperties)
{
var lambda = prop.ToString();
var columnName = lambda.Substring(lambda.IndexOf('.') + 1);
var predicate = string.Format(
"({0} != null && {0}.ToUpper().Contains(#0))", columnName);
predicates.Add(predicate);
}
var filter = string.Join("||", predicates);
var results = data.Where(filter, searchTerm);
return results;
}
Usage.
var retailers = Match(
"asd", db.Retailers, r => r.CompanyName, r => r.TradingName);
var retailers = Match(
"asd", db.Retailers, r => r.Address.Street, r => r.Address.Complement);
Limitation.
The filter can only accept basic expression.
r => r.Name
r => r.PropA.Name
r => r.PropA.PropB.Name
Try to use Expressions like those all
http://www.codeproject.com/Articles/493917/Dynamic-Querying-with-LINQ-to-Entities-and-Express
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);
So what I am trying to do is use expression trees to apply a predicate to each value in a collection (read map or list.All(predicate)). It appears that I am not getting the input parameter to the predicate bound to the value supplied by All, and I'm a little stuck. Here is the code (using linqpad) that I am working with::
public class SomeType
{
public IEnumerable<bool> Collection { get; set; }
}
void Main()
{
var list = new SomeType {
Collection = new List<bool> { true, true, true }
};
var functor = Compiler((SomeType t) => t.Collection, (bool x) => x);
functor(list).Dump();
}
MethodInfo FindMethod<TInput>(Type location, string name)
{
var handle = location
.GetMethods(BindingFlags.Static | BindingFlags.Public)
.Where(method => method.Name == name).First();
return handle.MakeGenericMethod(typeof(TInput));
}
Predicate<TObject> Compiler<TObject, TProperty>(
Expression<Func<TObject, IEnumerable<TProperty>>> selector,
Expression<Predicate<TProperty>> predicate)
{
var query = FindMethod<TProperty>(typeof(Enumerable), "All");
var expression = Expression.Call(query,
new Expression[] {
Expression.Invoke(selector, selector.Parameters),
Expression.Lambda<Func<TProperty, bool>>(predicate.Body,
Expression.Parameter(typeof(TProperty))),
});
return Expression.Lambda<Predicate<TObject>>(expression,
selector.Parameters).Compile();
}
Thanks and sorry if this was answered in another question (I looked for a while).
This does work, but I had to change the Predicate<TObject> to Func<TObject, bool>. If you want I can try to change it back.
static Predicate<TObject> Compiler<TObject, TProperty>(
Expression<Func<TObject, IEnumerable<TProperty>>> selector,
Expression<Func<TProperty, bool>> predicate)
{
var query = FindMethod<TProperty>(typeof(Enumerable), "All");
var expression = Expression.Call(
query,
Expression.Invoke(selector, selector.Parameters),
predicate);
return Expression
.Lambda<Predicate<TObject>>(expression, selector.Parameters)
.Compile();
}
5 minutes later... And if you really want to use Predicate<TObject>...
static Predicate<TObject> Compiler<TObject, TProperty>(
Expression<Func<TObject, IEnumerable<TProperty>>> selector,
Expression<Predicate<TProperty>> predicate)
{
var query = FindMethod<TProperty>(typeof(Enumerable), "All");
var predicateAsFunc = Expression.Lambda<Func<TProperty, bool>>(
predicate.Body,
predicate.Parameters);
var expression = Expression.Call(
query,
Expression.Invoke(selector, selector.Parameters),
predicateAsFunc);
return Expression
.Lambda<Predicate<TObject>>(expression, selector.Parameters)
.Compile();
}