如何使用 Entity Framework 查找所有搜索词都包含在选定列中的行
How to find a row where all search terms are contained within selected columns using Entity Framework
就上下文而言,这个问题与 and . In this case, the user can specify an array of phrases. I'd like to expand upon the 有关,询问我如何创建一种通用方法来查找 所有 单词的实体any 个短语包含在 any 个指定列中。
为了让您更好地理解我在说什么,如果我要将其编写为非泛型方法,它看起来会像这样:
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()
的调用让我很困惑。
像谓词 for
的表达式
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
说
The nested query is not supported. Operation1='Case' Operation2='Collect'
动态构建的查询也会发生同样的情况。
那我们该怎么办呢?好吧,考虑到 searchPhrases
(因此 searchTermSets
和 searchTerms
)是已知的,我们可以将它们视为常量,我们需要获得所需结果的所有内容是替换 Any
与 Or
表达式和 All
与 And
表达式。
工作解决方案如下所示(使用相同的参数替换器):
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 body = searchTermSets
.Select(searchTerms => searchTerms
.Select(searchTerm => propertySelectors
.Select(propertySelector => (Expression)Expression.Call(
propertySelector.Body.ReplaceParameter(propertySelector.Parameters[0], c),
"Contains", Type.EmptyTypes, Expression.Constant(searchTerm)))
.Aggregate(Expression.OrElse))
.Aggregate(Expression.AndAlso))
.Aggregate(Expression.OrElse);
var predicate = Expression.Lambda<Func<T, bool>>(body, c);
return source.Where(predicate);
}
}
就上下文而言,这个问题与
为了让您更好地理解我在说什么,如果我要将其编写为非泛型方法,它看起来会像这样:
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()
的调用让我很困惑。
像谓词 for
的表达式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
说
The nested query is not supported. Operation1='Case' Operation2='Collect'
动态构建的查询也会发生同样的情况。
那我们该怎么办呢?好吧,考虑到 searchPhrases
(因此 searchTermSets
和 searchTerms
)是已知的,我们可以将它们视为常量,我们需要获得所需结果的所有内容是替换 Any
与 Or
表达式和 All
与 And
表达式。
工作解决方案如下所示(使用相同的参数替换器):
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 body = searchTermSets
.Select(searchTerms => searchTerms
.Select(searchTerm => propertySelectors
.Select(propertySelector => (Expression)Expression.Call(
propertySelector.Body.ReplaceParameter(propertySelector.Parameters[0], c),
"Contains", Type.EmptyTypes, Expression.Constant(searchTerm)))
.Aggregate(Expression.OrElse))
.Aggregate(Expression.AndAlso))
.Aggregate(Expression.OrElse);
var predicate = Expression.Lambda<Func<T, bool>>(body, c);
return source.Where(predicate);
}
}