У меня есть строка поиска, которая может быть "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; }
}
Если это условие И, вы можете просто вызвать .Where для каждого фрагмента. Если вам нужно объединить выражения ИЛИ, вы можете сделать это, как gist.github.com/SamWM/2029839#file-linqextensionmethods-cs-L13. Я бы построил выражение с нуля только в том случае, если вам нужно сравнить свойство по имени.
@ gunr2171: я изменил свой вопрос. Я надеюсь, что теперь это более ясно. BaseSpecifcation принимает только Expression<Func<T, bool>>
Возможно, я бы переосмыслил ваш подход. Как ваше предлагаемое решение должно знать, какую строковую переменную в выражении разделить (у вас есть 2, одна из них .Name)? Если вы действительно хотите переписать выражение во время выполнения, посмотрите stackoverflow.com/q/11159697/1462295 и подобные. В противном случае похоже, что у вас есть доступ к конструктору ProductsSpecification. Я бы добавил метод/фабричный класс/что-то, чтобы изменить способ вызова конструктора BaseSpecifcation, и вы можете разделить свою строку в этой точке, чтобы передать все .Contains(s1) || .Contains(s2) || ... .
Оставил несколько невысказанных предположений в моем комментарии, в основном существует ограничение на количество подстрок, которые вы хотите потенциально искать, поэтому поддержка короткого явного списка не так сложна.
Количество подстрок не ограничено. Это динамично.
Прежде всего, я создал вспомогательный класс для генерации предикатов:
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
}
Теперь фильтр работает!
Может быть, я не совсем понимаю, что вы пытаетесь сделать, но почему бы и нет string.Split()? Почему выражения?