Hello. There was a very difficult question that I have never encountered. I need to create a method that will understand by what property the filter needs to be made. Ie, let's say I have a class

public class User { public string Name {get; set;} public string Nick {get; set;} } 

And I need to pull some users out of the database, but the criterion is not known in advance, in the query Name or Nick can be null.

At the moment it looks like this:

 //это часть когда находится в классе user IQarable<User> query ... тут создается query и передается в метод ниже ... if (!string.IsNullOrEmpty(Name)) { query = query.Where(x => x.VenueName.Contains(Venue)); } if (!string.IsNullOrEmpty(Nick)) { query = query.Where(x => x.City.Contains(City)); } //и так далее 

Inside the If blocks, there is some other check, which is why I try to bring this into method 1, but that's not the point.

I'm trying to make a method that takes a query and a property in the form of a string, which needs to be executed Where (...), so that it would look like this

 if (!string.IsNullOrEmpty(Name)) { query = SearchMethod(query, "Name", "Jhon"); } 

I can’t imagine how I can replace the Where(x => x./*тут свойство, которое каким-то образом определено*/.Contais("SearchingValue")) is something else that can be calculated by the property I search and substitute it in this expression. By reflection I was able to get only the property itself.

 Type t = this.GetType(); PropertyInfo prop = t.GetProperty("EventName"); 

I ask your help in solving this problem.

  • 2
    And why do you want to transfer the property name exactly as a string? Why not in the form of lambda expression? - VladD
  • to get a value from PropertyInfo, you can use the GetValue method: prop.GetValue(this) . - Grundy
  • @Grundy so the value of the property I pass to the method, thanks, by the way, I thought why my exeption crashed when I wrote prop.GetValue (prop) - simply good
  • @VladD I would at least like to pass it on, it just seemed like the easiest way to work with the string is simply good
  • one
    Possible duplicate question: Dynamic Linq query construction - Grundy

2 answers 2

IQueryable cannot be filtered by Func<T, bool> . (You’ll get an IEnumerable .) To save IQueryable you’ll have to build an Expression (and it seems to be manual). Here is the documentation .

For your case, if you need to compare the value with a constant, you can do this (not tested, departures are possible in runtime):

 IQueryable<T> Filter<T, V>(IQueryable<T> original, Expression<Func<T, V>> еxtractor, V value) { return original.Where(ProjectionEquals(еxtractor, value)); } Expression<Func<T, bool>> ProjectionEquals<T, V>(Expression<Func<T, V>> еxtractor, V value) { var body = Expression.Equal(еxtractor.Body, Expression.Constant(value)); return Expression.Lambda<Func<T, bool>>(body, еxtractor.Parameters[0]); } 

Use this:

 query = Filter(query, x => x.Name, "Jhon"); 

Inside Filter you can wind up, of course, more complex logic.


If you still really want to lose checking at the compilation stage and pass property names as strings, you can:

 IQueryable<T> Filter<T, V>(IQueryable<T> original, string propName, V value) { return original.Where(PropertyEquals<T, V>(propName, value)); } Expression<Func<T, bool>> PropertyEquals<T, V>(string propName, V value) { var parameter = Expression.Parameter(typeof(T), "t"); var left = Expression.PropertyOrField(parameter, propName); var body = Expression.Equal(left, Expression.Constant(value)); return Expression.Lambda<Func<T, bool>>(body, parameter); } 

and use this:

 query = Filter(query, "Name", "Jhon"); 

Do you understand the scheme?


For example, if you need Contains :

 Expression<Func<T, bool>> GetContains<T>(string propName, string value) { var parameter = Expression.Parameter(typeof(T), "t"); var prop = Expression.Property(parameter, propName); var containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) }); var valueAsExpr = Expression.Constant(value, typeof(string)); var contains = Expression.Call(propOrField, containsMethod, valueAsExpr); return Expression.Lambda<Func<T, bool>>(contains, parameter); } 

As the @Pavel Mayorov comment suggests, the last function can be rewritten more simply:

 Expression<Func<T, bool>> GetContains<T>(string propName, string value) { var parameter = Expression.Parameter(typeof(T), "t"); var prop = Expression.Property(parameter, propName); var valueAsExpr = Expression.Constant(value, typeof(string)); var contains = Expression.Call(prop, "Contains", null, valueAsExpr); return Expression.Lambda<Func<T, bool>>(contains, parameter); } 
  • Thank you, I will try to understand your scheme. My friend helped me with this question, he also got it. seems to be similar to your case, but nevertheless, now I’ll throw this option into the answers too - simply good
  • PropertyOrField redundant - all the ORMs I know do not know how to work with fields. - Pavel Mayorov
  • also does not need to containsMethod explicitly - Expression.Call can find the method itself by name: Expression.Call(prop, "Contains", null, valueAsExpr) - Pavel Mayorov
  • @PavelMayorov: Well, about the fields, yes. But it is better to show excess functionality than insufficient. - VladD
  • @PavelMayorov: I didn’t know about Expression.Call . I was looking for a method not to construct lambdas manually, but to set something like return t => extractor(t).Contains(value) for the Expression 'and the extractor . But so, apparently, is impossible. - VladD

The source code I got this

 if (!string.IsNullOrEmpty(EventName)) { query = query.ContainsOrStartWithQuery(x => x.EventName, EventName); } if (!string.IsNullOrEmpty(Venue)) { query = query.ContainsOrStartWithQuery(x => x.VenueName, Venue); } 

The method itself is in a static class, it turned out to be quite large.

 public static class ExpressionHelper { #region EgorAdded private static MethodInfo containsMethod; private static MethodInfo startsWithMethod; static ExpressionHelper() { containsMethod = typeof(string).GetMethods().First(m => m.Name == "Contains" && m.GetParameters().Length == 1); startsWithMethod = typeof(string).GetMethods().First(m => m.Name == "StartsWith" && m.GetParameters().Length == 1); } public static Expression<Func<T, bool>> AddContains<T>(this Expression<Func<T, string>> selector, string value) { var body = selector.GetBody().AsString(); var x = Expression.Call(body, containsMethod, Expression.Constant(value)); LambdaExpression e = Expression.Lambda(x, selector.Parameters.ToArray()); return (Expression<Func<T, bool>>)e; } public static Expression<Func<T, bool>> AddStartsWith<T>(this Expression<Func<T, string>> selector, string value) { var body = selector.GetBody().AsString(); var x = Expression.Call(body, startsWithMethod, Expression.Constant(value)); LambdaExpression e = Expression.Lambda(x, selector.Parameters.ToArray()); return (Expression<Func<T, bool>>)e; } private static Expression GetBody(this LambdaExpression expression) { Expression body; if (expression.Body is UnaryExpression) body = ((UnaryExpression)expression.Body).Operand; else body = expression.Body; return body; } private static Expression AsString(this Expression expression) { if (expression.Type == typeof(string)) return expression; MethodInfo toString = typeof(SqlFunctions).GetMethods().First(m => m.Name == "StringConvert" && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType == typeof(double?)); var cast = Expression.Convert(expression, typeof(double?)); return Expression.Call(toString, cast); } public static IQueryable<TEntity> ContainsOrStartWithQuery<TEntity>(this IQueryable<TEntity> query, Expression<Func<TEntity, string>> selector, string search) { if (search.StartsWith("*")) { search = search.Substring(1); query = query.Where(selector.AddContains<TEntity>(search)); } else { query = query.Where(selector.AddStartsWith<TEntity>(search)); } return query; } } 

https://stackoverflow.com/questions/16460057/call-contains-method-in-linq-to-entities-expression-on-a-type-other-than-strin

  • one
    Your .ToArray() redundant - there are overloads that take an enumeration. - Pavel Mayorov