Создать дерево лямбда-выражений, Expression<Func<T, bool>>, динамически выполняющее предикат на основе Or и And

У меня есть строка поиска, которая может быть "sub1 sub2 sub3", и я хочу написать правильный Expression<Func<T, bool>>, который может найти "sub1", "sub2", "sub3" и "sub1 sub2 sub3" в x.Name

С другой стороны, я хочу изменить x.Name.ToLower().Contains(productParams.Search) для своей цели. Теперь я могу искать термин "sub1 sub2 sub3". Тем не менее, я хочу искать и подстроки.

Мое ожидание от поиска: "sub1" || "sub2" || "sub3" || "sub1 sub2 sub3"

productParams.Search = "sub1 sub2 sub3"

Как это сделать?

public class ProductsSpecification : BaseSpecifcation<Product>
{
   public ProductsSpecification(ProductSpecParams productParams) : base(x =>
      (string.IsNullOrEmpty(productParams.Search) || 
      x.Name.ToLower().Contains(productParams.Search)) &&
      (!productParams.BrandId.HasValue || x.ProductBrandId == productParams.BrandId))
}

Базовая спецификация:

public class BaseSpecifcation<T> : ISpecification<T>
{
    public BaseSpecifcation(Expression<Func<T, bool>> criteria)
    {
        Criteria = criteria;
    }

    public Expression<Func<T, bool>> Criteria { get; }
}

Может быть, я не совсем понимаю, что вы пытаетесь сделать, но почему бы и нет string.Split()? Почему выражения?

gunr2171 21.11.2022 00:42

Если это условие И, вы можете просто вызвать .Where для каждого фрагмента. Если вам нужно объединить выражения ИЛИ, вы можете сделать это, как gist.github.com/SamWM/2029839#file-linqextensionmethods-cs-L‌​13. Я бы построил выражение с нуля только в том случае, если вам нужно сравнить свойство по имени.

Jeremy Lakeman 21.11.2022 00:48

@ gunr2171: я изменил свой вопрос. Я надеюсь, что теперь это более ясно. BaseSpecifcation принимает только Expression<Func<T, bool>>

x19 21.11.2022 01:02

Возможно, я бы переосмыслил ваш подход. Как ваше предлагаемое решение должно знать, какую строковую переменную в выражении разделить (у вас есть 2, одна из них .Name)? Если вы действительно хотите переписать выражение во время выполнения, посмотрите stackoverflow.com/q/11159697/1462295 и подобные. В противном случае похоже, что у вас есть доступ к конструктору ProductsSpecification. Я бы добавил метод/фабричный класс/что-то, чтобы изменить способ вызова конструктора BaseSpecifcation, и вы можете разделить свою строку в этой точке, чтобы передать все .Contains(s1) || .Contains(s2) || ... .

BurnsBA 21.11.2022 16:45

Оставил несколько невысказанных предположений в моем комментарии, в основном существует ограничение на количество подстрок, которые вы хотите потенциально искать, поэтому поддержка короткого явного списка не так сложна.

BurnsBA 21.11.2022 16:50

Количество подстрок не ограничено. Это динамично.

x19 21.11.2022 18:15
Как настроить Tailwind CSS с React.js и Next.js?
Как настроить Tailwind CSS с React.js и Next.js?
Tailwind CSS - единственный фреймворк, который, как я убедился, масштабируется в больших командах. Он легко настраивается, адаптируется к любому...
LeetCode запись решения 2536. Увеличение подматриц на единицу
LeetCode запись решения 2536. Увеличение подматриц на единицу
Увеличение подматриц на единицу - LeetCode
Переключение светлых/темных тем
Переключение светлых/темных тем
В Microsoft Training - Guided Project - Build a simple website with web pages, CSS files and JavaScript files, мы объясняем, как CSS можно...
Отношения &quot;многие ко многим&quot; в Laravel с методами присоединения и отсоединения
Отношения &quot;многие ко многим&quot; в Laravel с методами присоединения и отсоединения
Отношения "многие ко многим" в Laravel могут быть немного сложными, но с помощью Eloquent ORM и его моделей мы можем сделать это с легкостью. В этой...
В PHP
В PHP
В большой кодовой базе с множеством различных компонентов классы, функции и константы могут иметь одинаковые имена. Это может привести к путанице и...
Карта дорог Беладжар PHP Laravel
Карта дорог Беладжар PHP Laravel
Laravel - это PHP-фреймворк, разработанный для облегчения разработки веб-приложений. Laravel предоставляет различные функции, упрощающие разработку...
0
6
65
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Прежде всего, я создал вспомогательный класс для генерации предикатов:

 public static class PredicateBuilder
    {
        public static Expression<Func<T, bool>> True<T>() { return f => true; }
        public static Expression<Func<T, bool>> False<T>() { return f => false; }

        public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
        {
            var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
            return Expression.Lambda<Func<T, bool>> (Expression.OrElse(expr1.Body, invokedExpr), expr1.Parameters);
        }

        public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
        {
            var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
            return Expression.Lambda<Func<T, bool>> (Expression.AndAlso(expr1.Body, invokedExpr), expr1.Parameters);
        }
    }

Я создал закрытый метод в ProductsSpecification и использовал свой помощник класса:

private Expression<Func<Product, bool>> CreateProductFilter(ProductSpecParams productParams)
        {
            Expression<Func<Product, bool>> pr = PredicateBuilder.True<Product>(); // pr.Body.ToString() is "True"

            if (!string.IsNullOrEmpty(productParams.Search) && !string.IsNullOrEmpty(productParams.Search.Trim()))
            {
                var searchValue = productParams.Search.Trim().ToLower();
                pr = pr.And(a => a.Name.ToLower().Contains(searchValue));

                foreach (var term in productParams.Search.ToLower().Split(' '))
                {
                    string temp = term.Trim();
                    pr = pr.Or(a => a.Name.ToLower().Contains(temp));
                }
            }
            if (productParams.BrandId.HasValue)
            {
                pr = pr.And(p => p.ProductBrandId == productParams.BrandId);
            }
            

            if (pr.Body.ToString() == "True")
            {
                return null;
            }

            return pr;
        }

Я изменил конструкцию ProductsSpecification:

public ProductsSpecification(ProductSpecParams productParams) : base()
    {
        Criteria = CreateProductFilter(productParams);

        // rest of the code
        
    }

Теперь фильтр работает!

Другие вопросы по теме