简体   繁体   English

使用 String.Contains 的 EF Core 查询

[英]EF Core query using String.Contains

We have a requirement to searches a given term within a comma-separated string.我们需要在逗号分隔的字符串中搜索给定的术语。 The query is built so that it ignores possible leading and trailing spaces in the comma-separated string.构建查询以便它忽略逗号分隔字符串中可能的前导和尾随空格。 I came up with the following query which is running fine with EF 6.0我想出了以下查询,它在 EF 6.0 上运行良好

var trimmedTags = tags.Select(t => t.Trim()); // List of tags we need to look for    
return products.Where(p => trimmedTags.Any(t => ("," + p.Categories + ",").Contains("," + t + ",")) ||
                               trimmedTags.Any(t => ("," + p.Categories + ",").Contains(", " + t + ",")) ||
                               trimmedTags.Any(t => ("," + p.Categories + ",").Contains("," + t + " ,")) ||
                               trimmedTags.Any(t => ("," + p.Categories + ",").Contains(", " + t + " ,")));

This query is no longer running in EF Core 3.1 and throws the following error:此查询不再在 EF Core 3.1 中运行并引发以下错误:

System.InvalidOperationException: 'The LINQ expression 'DbSet<Product>
    .Where(p => __trimmedTags_1
        .Any(t => ("," + p.Categories + ",").Contains("," + t + ",")) || __trimmedTags_1
        .Any(t => ("," + p.Categories + ",").Contains(", " + t + ",")) || __trimmedTags_1
        .Any(t => ("," + p.Categories + ",").Contains("," + t + " ,")) || __trimmedTags_1
        .Any(t => ("," + p.Categories + ",").Contains(", " + t + " ,")))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.'

My target table has millions of rows so client evaluation is unfortunately not an option.我的目标表有数百万行,因此很遗憾客户评估不是一种选择。 The EF Core team claims that string.Contains is supported but I can't figure out why my query is suddenly failing in EF Core. EF Core 团队声称支持string.Contains ,但我无法弄清楚为什么我的查询在 EF Core 中突然失败。

A different variations of this question often appear on SO, and the problem is always one and the same - even at the most current version (5.x) EF Core does not support operators on in-memory collections other than simple Contains with primitive value (or Any that can be turned into Contains like x => memValues.Any(v => v == SomeExpr(x)) , with == operator being the essential).这个问题的不同变体经常出现在 SO 上,而且问题总是相同的 - 即使在最新版本 (5.x) 中,EF Core 也不支持内存中 collections 上的运算符,而不是简单的Contains原始值(或Any可以变成Contains的内容,例如x => memValues.Any(v => v == SomeExpr(x)) ,其中==运算符是必不可少的)。

The workaround is also one and the same - building dynamically expression - ||解决方法也是一样的——动态构建表达式—— || (or) based for Any and && (and) based for All . (or) based for Any&& (and) based for All

This case requires ||本案例需要|| , and is similar to How to simplify repetitive OR condition in Where(e => e.prop1.contains() || e.prop2.contains() ||...) but with value and field roles exchanged, so following is the helper method I would use: ,并且类似于如何简化 Where(e => e.prop1.contains() || e.prop2.contains() ||...) 中的重复 OR 条件,但交换了值和字段角色,因此以下是我将使用的辅助方法:

public static partial class QueryableExtensions
{
    public static IQueryable<T> WhereAnyMatch<T, V>(this IQueryable<T> source, IEnumerable<V> values, Expression<Func<T, V, bool>> match)
    {
        var parameter = match.Parameters[0];
        var body = values
            // the easiest way to let EF Core use parameter in the SQL query rather than literal value
            .Select(value => ((Expression<Func<V>>)(() => value)).Body)
            .Select(value => Expression.Invoke(match, parameter, value))
            .Aggregate<Expression>(Expression.OrElse);
        var predicate = Expression.Lambda<Func<T, bool>>(body, parameter);
        return source.Where(predicate);
    }
}

Note that this works only for top level query expressions.请注意,这仅适用于顶级查询表达式。 If you need something like this for something which is part of a query expression tree (like collection navigation property), you'd need different type of helper function or some library which allows expression injection.如果您需要这样的东西作为查询表达式树的一部分(如集合导航属性),您需要不同类型的帮助器 function 或一些允许表达式注入的库。

Luckily that's not the case here, so the above helper method can be used directly by passing the trimmedTags and the condition for each tag value, eg幸运的是,这里不是这种情况,因此可以通过传递trimmedTags和每个标签值的条件来直接使用上述辅助方法,例如

return products.WhereAnyMatch(trimmedTags, (p, t) => 
    ("," + p.Categories + ",").Contains("," + t + ",") ||
    ("," + p.Categories + ",").Contains(", " + t + ",") ||
    ("," + p.Categories + ",").Contains("," + t + " ,") ||
    ("," + p.Categories + ",").Contains(", " + t + " ,"));

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM