Может ли EF.Свойство в ядре EF работает со связанными свойствами объекта

#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);
        }
    }