如何使用Entity Framework查找所选搜索项包含在所选列中的行

时间:2016-09-02 22:30:57

标签: c# entity-framework linq linq-to-entities entity-framework-6

对于上下文,此问题与this questionthis 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()的这一呼吁让我感到沮丧。

1 个答案:

答案 0 :(得分:2)

表达式与

的谓词类似
contacts.Where(c =>
    searchTermSets.Any(searchTerms =>
        searchTerms.All(searchTerm =>
            c.FullName.Contains(searchTerm)
            || c.FirstName.Contains(searchTerm)
            || c.LastName.Contains(searchTerm)))); 

可以使用Expression.CallEnumerable.AnyEnumerable.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(因此searchTermSetssearchTerms)已知,我们可以将它们视为常量,我们需要获得所需的结果就是替换{{1 {}包含Any个表达式,Or包含All个表达式。

工作解决方案看起来像这样(使用相同的参数替换器):

And