Страницы

Поиск по вопросам

понедельник, 12 ноября 2018 г.

Определение свойства, по которому нужно произвести фильтр

Здравствуйте. Возник очень тяжелый вопрос с которым я никогда не сталкивался. мне нужно создать метод, который поймет по какому свойству нужно сделать фильтр. Т.е., допустим у меня есть класс
public class User { public string Name {get; set;} public string Nick {get; set;} }
И мне нужно вытащить из базы некоторых пользователей но критерий заранее не известен, в запросе Name или Nick могут быть null.
В данный момент это выглядит примерно так:
//это часть когда находится в классе user IQarable 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));
} //и так далее
Внутри блоков If есть еще кое какая проверка, вот поэтому я пытаюсь это вынести в 1 метод, но не в этом суть.
Я пытаюсь сделать метод, который принимает query и свойство в виде строки, по которому нужно выполнить Where(...), что бы это выглядело так
if (!string.IsNullOrEmpty(Name)) { query = SearchMethod(query, "Name", "Jhon"); }
Я не могу представить как мне заменить выражение Where(x => x./*тут свойство, которое каким-то образом определено*/.Contais("SearchingValue")) что-то другое, что может вычислить свойство по которому я веду поиск, и подставить его в это выражение. По рефлексии я смог получить только само свойство.
Type t = this.GetType(); PropertyInfo prop = t.GetProperty("EventName");
Прошу вашей помощи в решении этой проблемы.


Ответ

Отфильтровать IQueryable по Func нельзя. (У вас получится IEnumerable.) Для сохранения IQueryable вам придётся строить Expression (и кажется, вручную). Вот документация
Для вашего случая, если нужно сравнивать значение с константой, можно сделать так (не тестировал, возможны вылеты в рантайме):
IQueryable Filter(IQueryable original, Expression> еxtractor, V value) { return original.Where(ProjectionEquals(еxtractor, value)); }
Expression> ProjectionEquals(Expression> еxtractor, V value) { var body = Expression.Equal(еxtractor.Body, Expression.Constant(value)); return Expression.Lambda>(body, еxtractor.Parameters[0]); }
Пользоваться так:
query = Filter(query, x => x.Name, "Jhon");
Внутри Filter можно накрутить, понятно, более сложную логику.

Если всё же очень хочется потерять проверки на этапе компиляции и передавать имена свойств как строки, можно так:
IQueryable Filter(IQueryable original, string propName, V value) { return original.Where(PropertyEquals(propName, value)); }
Expression> PropertyEquals(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>(body, parameter); }
и пользоваться так:
query = Filter(query, "Name", "Jhon");
Понятна схема?

Для примера, если вам нужно Contains
Expression> GetContains(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>(contains, parameter); }

Как подсказывает в комментарии @Pavel Mayorov, последнюю функцию можно переписать проще:
Expression> GetContains(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>(contains, parameter); }

Комментариев нет:

Отправить комментарий