EF Core 全文搜索:无法将参数化的 keySelector 翻译成 SQL
EF Core Full-text search: parameterized keySelector could not be translated into SQL
我想创建一个允许我使用全文搜索的通用扩展方法。
● 下面的代码有效:
IQueryable<MyEntity> query = Repository.AsQueryable();
if (!string.IsNullOrEmpty(searchCondition.Name))
query = query.Where(e => EF.Functions.Contains(e.Name, searchCondition.Name));
return query.ToList();
● 但我想要一种更通用的方式,所以我创建了以下扩展方法
public static IQueryable<T> FullTextContains<T>(this IQueryable<T> query, Func<T, string> keySelector, string value)
{
return query.Where(e => EF.Functions.Contains(keySelector(e), value));
}
当我像下面这样调用扩展方法时,出现异常
IQueryable<MyEntity> query = Repository.AsQueryable();
if (!string.IsNullOrEmpty(searchCondition.Name))
query = query.FullTextContains(e => e.Name, searchCondition.Name);
return query.ToList();
> System.InvalidOperationException: 'The LINQ expression 'DbSet
> .Where(c => __Functions_0
> .Contains(
> _: Invoke(__keySelector_1, c[MyEntity])
> ,
> propertyReference: __value_2))' 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
>
我如何"rewrite the query in a form that can be translated" 按照异常建议?
您正在使用委托 (Func<>
) 而不是表达式 (Expression<Func<>
) 落入典型的 IQueryable<>
陷阱。您可以看到每个 Queryable
扩展方法的 lambda 参数与相应的 Enumerable
方法的差异。不同之处在于委托不能被翻译(它们就像未知的方法),而表达式可以。
所以为了做你想做的事,你必须更改自定义方法的签名以使用表达式:
public static IQueryable<T> FullTextContains<T>(
this IQueryable<T> query,
Expression<Func<T, string>> keySelector, // <--
string value)
但是现在你遇到了实现问题,因为 C# 不支持类似于委托的 "invoking" 表达式的语法,所以下面
keySelector(e)
不编译。
为此,您至少需要一个小型实用程序来编写如下表达式:
public static partial class ExpressionUtils
{
public static Expression<Func<TOuter, TResult>> Apply<TOuter, TInner, TResult>(this Expression<Func<TOuter, TInner>> outer, Expression<Func<TInner, TResult>> inner)
=> Expression.Lambda<Func<TOuter, TResult>>(inner.Body.ReplaceParameter(inner.Parameters[0], outer.Body), outer.Parameters);
public static Expression<Func<TOuter, TResult>> ApplyTo<TInner, TResult, TOuter>(this Expression<Func<TInner, TResult>> inner, Expression<Func<TOuter, TInner>> outer)
=> outer.Apply(inner);
public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target)
=> new ParameterReplacer { source = source, target = target }.Visit(expression);
class ParameterReplacer : ExpressionVisitor
{
public ParameterExpression source;
public Expression target;
protected override Expression VisitParameter(ParameterExpression node)
=> node == source ? target : node;
}
}
使用 Apply
或 ApplyTo
,具体取决于您的表达式类型。除此之外,他们做同样的事情。
在您的情况下,使用 Expression<Func<T, string>> keySelector
的方法的实现将是
return query.Where(keySelector.Apply(key => EF.Functions.Contains(key, value)));
我想创建一个允许我使用全文搜索的通用扩展方法。
● 下面的代码有效:
IQueryable<MyEntity> query = Repository.AsQueryable();
if (!string.IsNullOrEmpty(searchCondition.Name))
query = query.Where(e => EF.Functions.Contains(e.Name, searchCondition.Name));
return query.ToList();
● 但我想要一种更通用的方式,所以我创建了以下扩展方法
public static IQueryable<T> FullTextContains<T>(this IQueryable<T> query, Func<T, string> keySelector, string value)
{
return query.Where(e => EF.Functions.Contains(keySelector(e), value));
}
当我像下面这样调用扩展方法时,出现异常
IQueryable<MyEntity> query = Repository.AsQueryable();
if (!string.IsNullOrEmpty(searchCondition.Name))
query = query.FullTextContains(e => e.Name, searchCondition.Name);
return query.ToList();
> System.InvalidOperationException: 'The LINQ expression 'DbSet > .Where(c => __Functions_0 > .Contains( > _: Invoke(__keySelector_1, c[MyEntity]) > , > propertyReference: __value_2))' 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 >
我如何"rewrite the query in a form that can be translated" 按照异常建议?
您正在使用委托 (Func<>
) 而不是表达式 (Expression<Func<>
) 落入典型的 IQueryable<>
陷阱。您可以看到每个 Queryable
扩展方法的 lambda 参数与相应的 Enumerable
方法的差异。不同之处在于委托不能被翻译(它们就像未知的方法),而表达式可以。
所以为了做你想做的事,你必须更改自定义方法的签名以使用表达式:
public static IQueryable<T> FullTextContains<T>(
this IQueryable<T> query,
Expression<Func<T, string>> keySelector, // <--
string value)
但是现在你遇到了实现问题,因为 C# 不支持类似于委托的 "invoking" 表达式的语法,所以下面
keySelector(e)
不编译。
为此,您至少需要一个小型实用程序来编写如下表达式:
public static partial class ExpressionUtils
{
public static Expression<Func<TOuter, TResult>> Apply<TOuter, TInner, TResult>(this Expression<Func<TOuter, TInner>> outer, Expression<Func<TInner, TResult>> inner)
=> Expression.Lambda<Func<TOuter, TResult>>(inner.Body.ReplaceParameter(inner.Parameters[0], outer.Body), outer.Parameters);
public static Expression<Func<TOuter, TResult>> ApplyTo<TInner, TResult, TOuter>(this Expression<Func<TInner, TResult>> inner, Expression<Func<TOuter, TInner>> outer)
=> outer.Apply(inner);
public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target)
=> new ParameterReplacer { source = source, target = target }.Visit(expression);
class ParameterReplacer : ExpressionVisitor
{
public ParameterExpression source;
public Expression target;
protected override Expression VisitParameter(ParameterExpression node)
=> node == source ? target : node;
}
}
使用 Apply
或 ApplyTo
,具体取决于您的表达式类型。除此之外,他们做同样的事情。
在您的情况下,使用 Expression<Func<T, string>> keySelector
的方法的实现将是
return query.Where(keySelector.Apply(key => EF.Functions.Contains(key, value)));