#c_sharp #запрос #рефлексия
Здравствуйте. Возник очень тяжелый вопрос с которым я никогда не сталкивался. мне нужно создать метод, который поймет по какому свойству нужно сделать фильтр. Т.е., допустим у меня есть класс public class User { public string Name {get; set;} public string Nick {get; set;} } И мне нужно вытащить из базы некоторых пользователей но критерий заранее не известен, в запросе Name или Nick могут быть null. В данный момент это выглядит примерно так: //это часть когда находится в классе user IQarablequery ... тут создается 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"); Прошу вашей помощи в решении этой проблемы.
Ответы
Ответ 1
Отфильтровать 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); } Ответ 2
Исходный код у меня получился такой if (!string.IsNullOrEmpty(EventName)) { query = query.ContainsOrStartWithQuery(x => x.EventName, EventName); } if (!string.IsNullOrEmpty(Venue)) { query = query.ContainsOrStartWithQuery(x => x.VenueName, Venue); } Сам метод находится в статическом классе, он получился довольно большим. 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> AddContains (this Expression > 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 >)e; } public static Expression > AddStartsWith (this Expression > 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 >)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 ContainsOrStartWithQuery (this IQueryable query, Expression > selector, string search) { if (search.StartsWith("*")) { search = search.Substring(1); query = query.Where(selector.AddContains (search)); } else { query = query.Where(selector.AddStartsWith (search)); } return query; } } https://stackoverflow.com/questions/16460057/call-contains-method-in-linq-to-entities-expression-on-a-type-other-than-strin Руководствовался этим вопросом
Комментариев нет:
Отправить комментарий