【问题标题】:EF Core query using String.Contains使用 String.Contains 的 EF Core 查询
【发布时间】:2021-03-14 20:37:00
【问题描述】:

我们需要在逗号分隔的字符串中搜索给定的术语。构建查询以便它忽略逗号分隔字符串中可能的前导和尾随空格。 我想出了以下查询,它在 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 + " ,")));

此查询不再在 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.'

我的目标表有数百万行,因此很遗憾无法进行客户评估。 EF Core 团队声称支持string.Contains,但我不明白为什么我的查询在 EF Core 中突然失败。

【问题讨论】:

  • 我认为目前最好的办法是迭代 trimmedTags 并构建传递给 .Where 方法的表达式树。您可以生成查询 products.Where(p => "," + p.Categories + ",".Contains(",tag1,") || "," + p.Categories + ",".Contains(", tag2 ,") || "," + p.Categories + ",".Contains(",tag3,"));这应该可以正常工作。
  • 自从我使用 EF 以来已经有一段时间了,但是 trimmedTags 上的 ToList(在查询之外)是否解决了问题?或者您可以尝试使用 Ef.Functions.Like 并以某种方式简化比较
  • @dropoutcoder 感谢您的快速跟进。这正是我最终解决问题的方式。您能否将您的评论转换为答案,以便我接受?
  • @IvanStoev 他做到了,在下面一行:)
  • @GETah 似乎有人已经写了答案。我不是在寻找积分。我很乐意提供帮助:)

标签: c# ef-core-3.1


【解决方案1】:

这个问题的不同变体经常出现在 SO 上,而且问题总是相同的 - 即使在最新版本 (5.x) 中,EF Core 也不支持内存中集合上的运算符,而不是简单的 @ 987654322@ 具有原始值(或Any 可以转换为Contains,如x =&gt; memValues.Any(v =&gt; v == SomeExpr(x))== 运算符是必不可少的)。

解决方法也是一样的——动态构建表达式——||(或)基于Any&amp;&amp;(和)基于All

这种情况需要||,并且类似于How to simplify repetitive OR condition in Where(e => e.prop1.contains() || e.prop2.contains() || ...),但交换了值和字段角色,所以以下是我将使用的辅助方法:

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);
    }
}

请注意,这仅适用于顶级查询表达式。如果您需要这样的东西作为查询表达式树的一部分(如集合导航属性),您需要不同类型的辅助函数或允许表达式注入的库。

幸运的是,这里不是这种情况,因此可以通过传递trimmedTags 和每个标签值的条件来直接使用上述辅助方法,例如

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

【讨论】:

  • 太棒了!工作精美。非常感谢。
猜你喜欢
  • 2021-05-03
  • 2020-03-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多