对于上下文,此问题与this question和this question有关。在这种情况下,用户可以指定短语数组。我想通过询问如何创建一种通用方法来查找所有包含任何短语的单词的实体来扩展previous answer在指定列的任何中。
为了让您更好地了解我在谈论的内容,如果我要将其作为非泛型方法编写,它将看起来像这样:
var searchPhrases = new [] {"John Smith", "Smith Bob"};
var searchTermSets = searchPhrases.Select(x => x.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries));
contacts.Where(c =>
searchTermSets.Any(searchTerms =>
searchTerms.All(searchTerm =>
c.FullName.Contains(searchTerm)
|| c.FirstName.Contains(searchTerm)
|| c.LastName.Contains(searchTerm))));
我要做的是制作一个扩展方法,我可以这样做:
contact.WhereIn(
searchPhrases,
c => c.FullName,
c => c.FirstName,
c => c.LastName);
扩展方法签名看起来像这样:
IQueryable<T> WhereIn<T>(this IQueryable<T> source, IEnumerable<string> searchPhrases, params Expression<Func<T, string>>[] propertySelectors)
我尝试从之前与之相关的问题中遵循相同的模式,但我没有走得太远。对All()
的这一呼吁让我感到沮丧。
答案 0 :(得分:2)
表达式与
的谓词类似contacts.Where(c =>
searchTermSets.Any(searchTerms =>
searchTerms.All(searchTerm =>
c.FullName.Contains(searchTerm)
|| c.FirstName.Contains(searchTerm)
|| c.LastName.Contains(searchTerm))));
可以使用Expression.Call
到Enumerable.Any
和Enumerable.All
动态构建。
首先我们需要一个简单的参数替换器,这样我们就可以将所有传递的Expression<Func<T, string>>
绑定到一个参数:
public static class ExpressionUtils
{
public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target)
{
return new ParameterReplacer { Source = source, Target = target }.Visit(expression);
}
class ParameterReplacer : ExpressionVisitor
{
public ParameterExpression Source;
public Expression Target;
protected override Expression VisitParameter(ParameterExpression node)
{
return node == Source ? Target : base.VisitParameter(node);
}
}
}
然后实现可能是这样的:
public static class QueryableExtensions
{
public static IQueryable<T> WhereIn<T>(this IQueryable<T> source, IEnumerable<string> searchPhrases, params Expression<Func<T, string>>[] propertySelectors)
{
var searchTermSets = searchPhrases.Select(x => x.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries));
var c = Expression.Parameter(typeof(T), "c");
var searchTerms = Expression.Parameter(typeof(string[]), "searchTerms");
var searchTerm = Expression.Parameter(typeof(string), "searchTerm");
var allCondition = propertySelectors
.Select(propertySelector => (Expression)Expression.Call(
propertySelector.Body.ReplaceParameter(propertySelector.Parameters[0], c),
"Contains", Type.EmptyTypes, searchTerm))
.Aggregate(Expression.OrElse);
var allPredicate = Expression.Lambda<Func<string, bool>>(allCondition, searchTerm);
var allCall = Expression.Call(
typeof(Enumerable), "All", new[] { typeof(string) },
searchTerms, allPredicate);
var anyPredicate = Expression.Lambda<Func<string[], bool>>(allCall, searchTerms);
var anyCall = Expression.Call(
typeof(Enumerable), "Any", new[] { typeof(string[]) },
Expression.Constant(searchTermSets), anyPredicate);
var predicate = Expression.Lambda<Func<T, bool>>(anyCall, c);
return source.Where(predicate);
}
}
问题在于它不起作用。如果您尝试运行非通用查询,则会EntityCommandCompilationException
内部NotSupportedException
说
不支持嵌套查询。 Operation1 =&#39;案例&#39;操作2 =&#39;收集&#39;
动态构建的查询也会发生同样的情况。
那我们该怎么办?好吧,考虑到searchPhrases
(因此searchTermSets
和searchTerms
)已知,我们可以将它们视为常量,我们需要获得所需的结果就是替换{{1 {}包含Any
个表达式,Or
包含All
个表达式。
工作解决方案看起来像这样(使用相同的参数替换器):
And