使用 String.Contains 的 EF Core 查询
EF Core query using String.Contains
我们需要在逗号分隔的字符串中搜索给定的术语。构建查询以便忽略逗号分隔字符串中可能的前导和尾随空格。
我提出了以下查询,它 运行 适合 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.'
我的目标 table 有数百万行,因此很遗憾客户评估不是一个选项。 EF Core 团队声称 string.Contains
受支持,但我不明白为什么我的查询在 EF Core 中突然失败。
这个问题的不同变体经常出现在 SO 上,问题总是同一个 - 即使是最新版本 (5.x) EF Core 不支持内存集合上的运算符除了具有原始值的简单 Contains
(或 Any
可以像 x => memValues.Any(v => v == SomeExpr(x))
一样变成 Contains
,==
运算符是必不可少的)。
解决方法也是相同的 - 构建动态表达式 - ||
(或)基于 Any
和 &&
(和)基于 All
。
这种情况需要 ||
,与 类似,但交换了值和字段角色,因此以下是我将使用的辅助方法:
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 + " ,"));
我们需要在逗号分隔的字符串中搜索给定的术语。构建查询以便忽略逗号分隔字符串中可能的前导和尾随空格。 我提出了以下查询,它 运行 适合 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.'
我的目标 table 有数百万行,因此很遗憾客户评估不是一个选项。 EF Core 团队声称 string.Contains
受支持,但我不明白为什么我的查询在 EF Core 中突然失败。
这个问题的不同变体经常出现在 SO 上,问题总是同一个 - 即使是最新版本 (5.x) EF Core 不支持内存集合上的运算符除了具有原始值的简单 Contains
(或 Any
可以像 x => memValues.Any(v => v == SomeExpr(x))
一样变成 Contains
,==
运算符是必不可少的)。
解决方法也是相同的 - 构建动态表达式 - ||
(或)基于 Any
和 &&
(和)基于 All
。
这种情况需要 ||
,与
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 + " ,"));