Пишу Extension для IQueryablelist (EntityFramework)
IQueryablelist= db.Users;//Users table
//Поиск символа "a" в полях которые передаю, это FirstName or LastName
list.ApplyFilterPatern("a", x => x.FirstName, x => x.LastName,...);
//результат работы аналогичен коду
list.Where(x => x.FirstName.ToLower().Contains("a") || x.LastName.ToLower().Contains("a"))
public class User
{
public string FirstName
{
get;
set;
}
public string LastName
{
get;
set;
}
public string MiddleName
{
get;
set;
}
}
public class Program
{
public static void Main()
{
List<User> users = new List<User>();
users.Add(new User { FirstName = "a", LastName = "b" } );
users.Add(new User { FirstName = "b", LastName = "a" } );
users.Add(new User { FirstName = "b", LastName = "b" } );
IQueryable<User> l = users.AsQueryable<User>();
var r = ApplyFilterPatern(l, "a", x => x.FirstName, x => x.LastName);
}
public static IQueryable<T> ApplyFilterPatern<T>(IQueryable<T> source, string searchString, params Expression<Func<T, object>>[] properties)
{
Expression result = null;
foreach (var item in properties)
{
var lambda = item as LambdaExpression;
MemberExpression memberExpression;
if (lambda.Body is UnaryExpression)
{
var unaryExpression = (UnaryExpression)lambda.Body;
memberExpression = (MemberExpression)unaryExpression.Operand;
}
else
{
memberExpression = (MemberExpression)lambda.Body;
}
var expr = Expression.Call(memberExpression, "ToLower", null, null);
expr = Expression.Call(expr, typeof (string).GetMethod("Contains", new[] { typeof (string)}), Expression.Constant(searchString));
if (result == null)
result = expr;
else
result = Expression.Or(result, expr);
}
var parameter = Expression.Parameter(typeof (T), "x");
var r = Expression.Lambda<Func<T, bool>>(result, parameter);
Type predType = typeof (Func<, >).MakeGenericType(typeof (T), typeof (bool));
Type predType2 = typeof (Expression<>).MakeGenericType(predType);
MethodInfo mWhere = typeof (Queryable).GetMethods().Single(
method => method.Name == "Where"
&& method.IsGenericMethodDefinition
&& method.GetGenericArguments().Length == 1
&& method.GetParameters().Length == 2
&& method.GetParameters()[1].ParameterType.GetGenericArguments().Length == 1
&& method.GetParameters()[1].ParameterType.GetGenericArguments()[0].GetGenericArguments().Length == 2);
mWhere = mWhere.MakeGenericMethod(predType2);
mWhere.Invoke(null, new object[] { source, r } );
return source;
}
}
Run-time exception (line 82): Object of type 'System.Linq.EnumerableQuery`1[ASE.User]' cannot be converted to type 'System.Linq.IQueryable`1[System.Linq.Expressions.Expression`1[System.Func`2[ASE.User,System.Boolean]]]'.
Здравствуйте, Alexandr Sulimov, Вы писали:
AS>Run-time exception (line 82): Object of type 'System.Linq.EnumerableQuery`1[ASE.User]' cannot be converted to type 'System.Linq.IQueryable`1[System.Linq.Expressions.Expression`1[System.Func`2[ASE.User,System.Boolean]]]'.
1. Метод Where() резолвится не тот, что нужно.
2. Собирать итоговую лямбду из массива входящих лямбд и вновь созданного Expression.Parameter(typeof(T), "x") не прокатит, т.к. во входящих лямбдах свои собственные параметры, пусть даже и с тем же именем "x". В DebugView выглядит одинаково, но не работает.
3. Возвращать надо не source, а результат mWhere.Invoke().
4. Не Expression.Or, а Expression.OrElse.
В общем, поверхностный рефакторинг Вашего кода привел к этому:
public static IQueryable<T> ApplyFilterPatern<T>(IQueryable<T> source, string searchString, params Expression<Func<T, object>>[] properties)
{
var parameter = Expression.Parameter(typeof(T), "x");
Expression result = null;
foreach (var item in properties)
{
var lambda = item as LambdaExpression;
MemberExpression memberExpression;
if (lambda.Body is UnaryExpression)
{
var unaryExpression = (UnaryExpression)lambda.Body;
memberExpression = (MemberExpression)unaryExpression.Operand;
}
else
{
memberExpression = (MemberExpression)lambda.Body;
}
var propCallExpression = Expression.Property(parameter, (PropertyInfo)memberExpression.Member);
var expr = Expression.Call(propCallExpression, "ToLower", null, null);
expr = Expression.Call(expr, typeof (string).GetMethod("Contains", new[] { typeof (string)}), Expression.Constant(searchString));
if (result == null)
result = expr;
else
result = Expression.OrElse(result, expr);
}
MethodInfo mWhere =
(
from mi in typeof(Queryable).GetMethods(BindingFlags.Public | BindingFlags.Static)
where mi.Name == "Where"let paramInfos = mi.GetParameters()
where paramInfos.Length == 2
where (paramInfos[0].ParameterType.GetGenericTypeDefinition() == typeof(IQueryable<>))
let expressionGenericArgs = paramInfos[1].ParameterType.GetGenericArguments()
where expressionGenericArgs.Length == 1
where expressionGenericArgs[0].GetGenericTypeDefinition() == typeof(Func<,>)
select mi
)
.Single();
mWhere = mWhere.MakeGenericMethod(typeof(T));
var r = Expression.Lambda<Func<T, bool>>(result, parameter);
return (IQueryable<T>)mWhere.Invoke(null, new object[] { source, r });
}
Так даже вроде работает, но код все равно ужасен. Неужели сами не видите?
В целом, на мой взгляд, Вы явно выбрали не тот инструмент для решения задачи.
Expression-ы — это, конечно, звучит круто, но реальная необходимость их применения возникает исключительно редко.
И только в этих очень редких случаях стоит мириться с появлением в коде вышеприведенного месива.
Здравствуйте, scale_tone, Вы писали:
_>1. Метод Where() резолвится не тот, что нужно. _>2. Собирать итоговую лямбду из массива входящих лямбд и вновь созданного Expression.Parameter(typeof(T), "x") не прокатит, т.к. во входящих лямбдах свои собственные параметры, пусть даже и с тем же именем "x". В DebugView выглядит одинаково, но не работает. _>3. Возвращать надо не source, а результат mWhere.Invoke(). _>4. Не Expression.Or, а Expression.OrElse.
Огромная боагодарность, несколько дней игрался.
_>Так даже вроде работает, но код все равно ужасен. Неужели сами не видите? _>В целом, на мой взгляд, Вы явно выбрали не тот инструмент для решения задачи. _>Expression-ы — это, конечно, звучит круто, но реальная необходимость их применения возникает исключительно редко. _>И только в этих очень редких случаях стоит мириться с появлением в коде вышеприведенного месива.
А какой инструмент в этом случаее оптимальный?
Для динамических фильтров (сортирвок) я как раз и вышел на Expression.
Здравствуйте, Alexandr Sulimov, Вы писали:
AS>А какой инструмент в этом случаее оптимальный? AS>Для динамических фильтров (сортирвок) я как раз и вышел на Expression.
Можно, например, снабдить саму сущность методом, возвращающим выражение для поиска по ее текстовым полям:
public class User
{
public int UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string MiddleName { get; set; }
public static Expression<Func<User, bool>> GetFilterExpression(string filter)
{
return u =>
u.FirstName.ToLower().Contains(filter)
||
u.LastName.ToLower().Contains(filter)
;
}
}
Так, по крайней мере, код понятен. И каждой сущности можно приделать по своему такому методу.
И тогда:
foreach (var u in db.Users.Where(Models.User.GetFilterExpression("a")))
{
Debug.WriteLine(u.FirstName + " " + u.LastName);
}
Здравствуйте, scale_tone, Вы писали:
_>Здравствуйте, Alexandr Sulimov, Вы писали:
AS>>А какой инструмент в этом случаее оптимальный? AS>>Для динамических фильтров (сортирвок) я как раз и вышел на Expression.
_>Можно, например, снабдить саму сущность методом, возвращающим выражение для поиска по ее текстовым полям:
Будет ли тогда это корректно транслироваться в sql (Entity framework)?
_>
_>foreach (var u in db.Users.Where(Models.User.GetFilterExpression("a")))
_>{
_> Debug.WriteLine(u.FirstName + " " + u.LastName);
_>}
_>
Нужно обеспечить именно динамический набор полей для фильтрации через единый метод (расширение)
Здравствуйте, Alexandr Sulimov, Вы писали:
AS>Будет ли тогда это корректно транслироваться в sql (Entity framework)?
Ну так работает же. На EF-контексте и проверял.
AS>Нужно обеспечить именно динамический набор полей для фильтрации через единый метод (расширение)
Так вот что значит "динамически"? В Вашем-то варианте оно тоже ни разу не динамическое: набор полей, по которым искать, все равно получается захардкоженным.
А если у Вас задача дать пользователю возможность выбрать из набора полей в некоем окне поиска — то тут гораздо проще прямо SQL-запрос сгенерить. Или сделать хранимку. Чем генерить Expression, по которому потом EF сгенерит SQL-запрос...
Здравствуйте, scale_tone, Вы писали:
_>А если у Вас задача дать пользователю возможность выбрать из набора полей в некоем окне поиска — то тут гораздо проще прямо SQL-запрос сгенерить. Или сделать хранимку. Чем генерить Expression, по которому потом EF сгенерит SQL-запрос...
Не всегда проще...
Здравствуйте, fddima, Вы писали:
F> Упростить создание предиката можно с помощью чего-нибудь такого Dynamically Composing Expression Predicates.
Ребята — мне страшно стыдно, что за такую известную ссылку — я получил столько оценок.
Давайте теперь я напишу то что я думаю об этом сам. Действительно что думаю об этом сам.
Альбахари этого нужно на вилы — в первую очередь. Пишет дядька конечно умные вещи — но в моём случае, когда я нашел эту ссылку — это было уже игрушкой.
А вообще что я хотел и что я сделал: я хотел что бы было в приложении строка поиска "аля гугл". И в целом — даже получалось. При этом — вывод — абсолютно табличный, при чём конкретных (а не произвольных) сущностей.
В итоге получился in-house фреймворк который способен схавать схему запросов (суть в том что, запросы могут не просто понимать какие-то данные навроде поля=значение, а и просто какие-то "значения" которые позже могут быть во что-то смаплены). Иначе говоря появился довольно специфичный поисковый фреймворк. И да — генерил он эти предикаты далеко нечерез альбахаревскую нечисть (а смысл?!). Даже были идеи сделать его опенсорсным.
Позже пришло понимание и интеграция с Full-Text Search. Так вот — FTS работал из рук вон плохо. Предикат на строку он умудрялся из NVARCHAR конвертировать к VARCHAR и сравнивать его для каждого вхождения что давало ну 90% времени. Ну поверьте уже на слово — если строк хотя бы миллион — с таким говном даже не захочется связываться.
Проблема по факту даже не в этих мелочах, а в том что такие поисхки (которые хотелось) — сиквел в принципе не способен нормально хэндлить. (Т.е. он то нормально всё делает, но только не то, что хотелось бы — ну а по факту — что должен то и делает). Поэтому — вопрос возрос в куда более широкий, а потом вовсе это дело загнило — т.к. аля невостребовано (хотя оно востребовано). Просто потом проект совсем прикрылся. Но опыт — получен. А до того — с сиквелом как раз работали колоссально, скорее что я рассказал — это игрушки то как раз и были.
А. Ну так я что хотел сказать. Склеивание предикатов — путь очень скользкий. =) Каждый его делает видимо по своей нужде. Мне альбахари не подходил. Я предпочёл иметь схему моделей на основе которых могли делать запросы (не забываем, что запросы на самом деле с row-level security). И весь этот велосипед — работал прекрасно. Начиная от парсинья запросов — заканчивая их исполнением.
Но я этими своими крутыми разговорами хотел вот от чего предостереч — не делайте то, что вам не надо. А это такая область когда легко можно создать ещё одну библиотеку. Только когда я этим занималися — их и не было. А теперь — даже альбахари предлагает бредочек свой (бредочек — потому что это пред в практике — но очень удобный, и ещё это бредочек — потому что он работает именно только в этих рамках — а шаг-влево-вправо — выхватите). Не верите? Вперед!