[英]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.