#entity-framework-core
#entity-framework-core
Вопрос:
Я создаю функцию динамического фильтра, используя сборку в Ef Core EF.Функция свойства.
private static IQueryable<T> Filter<T>(this IQueryable<T> source, string propertyName, string filterValue)
{
return source.Where(c =>
EF.Functions.Like(EF.Property<string>(c, propertyName).ToLower(), $"{filterValue}%".ToLower()));
}
Он отлично работает с простыми свойствами, такими как имя и возраст, но если используется для свойства объекта, такого как Customer.CompanyName тогда это не удается.
Пример динамического запроса none с фильтром для Customer.Название компании
query = query.Where(x => EF.Functions.Like(x.Customer.CompanyName.ToLower(), ""));
Есть ли обходной путь?
Комментарии:
1.
if used on a object property
Это связанная сущность, а не свойство. Или, скорее, свойство для связанного объекта. Вы пытаетесь использовать метод не для того объекта. Кроме того, LINQ уже является динамическим, и в том, что вы опубликовали, есть по крайней мере одна ошибка убийцы, котораяToLower()
помешает серверу использовать индексы и , вероятно, вернет неправильные результаты. Равенство (и индексация) контролируется сопоставлением столбцов в большинстве баз данных с использованием сопоставления с учетом регистра . ВызовToLower()
вызовет вызовы функций, которые не позволяют базе данных использовать индексы, покрывающие этот столбец2. Кроме того, если разработчик / разработчик базы данных хотел, чтобы столбец учитывал регистр , у них была причина.
ToLower()
приведет к запросу, который «волшебным образом» игнорирует явную сортировку и возвращает неожиданные результаты без видимой причины. В конце концов, база данных остается чувствительной к регистру, запрос разработчика не вызываетсяToLower()
, так кто изменил параметры сортировки?3. Поиск должен быть нечувствительным к регистру. или это было бы не очень удобно для пользователя. Так что администратор базы данных знает об этом
4. Как я использую его не на том объекте? как мне тогда получить к нему доступ?
5. Вы также можете проверить не-Microsoft Microsoft. Пакет EntityFrameworkCore.DynamicLinq и его источник .
Ответ №1:
Итак, тогда EF.Property
это не очень хороший вариант. Следующий код содержит некоторую «магию» дерева выражений, но я надеюсь, что это будет полезно при создании других расширений.
public static IQueryable<T> Filter<T>(this IQueryable<T> source, string propertyName, string filterValue)
{
var param = Expression.Parameter(typeof(T), "e");
var body = (Expression) param;
foreach (var propName in propertyName.Split('.'))
{
body = Expression.PropertyOrField(body, propName);
}
body = Expression.Call(body, "ToLower", Type.EmptyTypes);
body = Expression.Call(typeof(DbFunctionsExtensions), "Like", Type.EmptyTypes,
Expression.Constant(EF.Functions), body, Expression.Constant($"{filterValue}%".ToLower()));
var lambda = Expression.Lambda(body, param);
var queryExpr = Expression.Call(typeof(Queryable), "Where", new[] {typeof(T)}, source.Expression, lambda);
return source.Provider.CreateQuery<T>(queryExpr);
}
Комментарии:
1. Спасибо, что нашли время. Я попробую код
2. Вы потрясающие (-: ваш код просто работает, и теперь мне просто нужно узнать о деревьях выражений. Еще раз спасибо, я работал над этой проблемой несколько дней
3. @MartinAndersen. Если вы хотите, я могу опубликовать более простую реализацию, но для этого нужен дополнительный
ReplacingVisitor
класс.4. Я думаю, что код очень понятен и удобочитаем. Мне просто нужно больше узнать о деревьях выражений
5. Да, но скучно выполнять такие сложные манипуляции, и есть более интуитивно понятный способ 😉
Ответ №2:
Другое решение, более сложное, но мощное. Идея состоит в том, чтобы дать компилятору C # возможность создавать дерево выражений и просто исправлять их в конце.
Использование простое, но многоразовое:
string filterValue = "A";
query = query.FilterByPattern("Customer.CompanyName",
(string v) => EF.Functions.Like(v.ToLower(), $"{filterValue}%"));
Та же функция фильтра, что и описанная в вопросе, написана с использованием этого расширения
public static IQueryable<T> Filter<T>(this IQueryable<T> query, string propPath, string filterValue)
{
return query.FilterByPattern(propPath,
(string v) => EF.Functions.Like(v.ToLower(), $"{filterValue}%"));
}
И реализация:
public static class QueryExtensions
{
public static IQueryable<T> FilterByPattern<T, TProp>(this IQueryable<T> query, string propPath,
Expression<Func<TProp, bool>> predicatePattern)
{
return query.Where(MakePredicate<T, TProp>(propPath, predicatePattern));
}
public static Expression GetPath(Expression obj, string propPath)
{
if (!string.IsNullOrEmpty(propPath))
{
foreach (var propName in propPath.Split('.'))
{
obj = Expression.PropertyOrField(obj, propName);
}
}
return obj;
}
public static Expression<Func<T, bool>> MakePredicate<T, TProp>(string propPath,
Expression<Func<TProp, bool>> predicatePattern)
{
var param = Expression.Parameter(typeof(T), "e");
var body = GetPath(param, propPath);
body = ExpressionReplacer.GetBody(predicatePattern, body);
return Expression.Lambda<Func<T, bool>>(body, param);
}
class ExpressionReplacer : ExpressionVisitor
{
readonly IDictionary<Expression, Expression> _replaceMap;
public ExpressionReplacer(IDictionary<Expression, Expression> replaceMap)
{
_replaceMap = replaceMap ?? throw new ArgumentNullException(nameof(replaceMap));
}
public override Expression Visit(Expression exp)
{
if (exp != null amp;amp; _replaceMap.TryGetValue(exp, out var replacement))
return replacement;
return base.Visit(exp);
}
public static Expression Replace(Expression expr, Expression toReplace, Expression toExpr)
{
return new ExpressionReplacer(new Dictionary<Expression, Expression> {{toReplace, toExpr}}).Visit(expr);
}
public static Expression Replace(Expression expr, IDictionary<Expression, Expression> replaceMap)
{
return new ExpressionReplacer(replaceMap).Visit(expr);
}
public static Expression GetBody(LambdaExpression lambda, params Expression[] toReplace)
{
if (lambda.Parameters.Count != toReplace.Length)
throw new InvalidOperationException();
return new ExpressionReplacer(lambda.Parameters.Zip(toReplace)
.ToDictionary(e => (Expression)e.First, e => e.Second)).Visit(lambda.Body);
}
}