Страницы

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

суббота, 21 декабря 2019 г.

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

#c_sharp #запрос #рефлексия


Здравствуйте. Возник очень тяжелый вопрос с которым я никогда не сталкивался. мне
нужно создать метод, который поймет по какому свойству нужно сделать фильтр. Т.е.,
допустим у меня есть класс 

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");


Прошу вашей помощи в решении этой проблемы.
    


Ответы

Ответ 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 Руководствовался этим вопросом

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

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