NotSupportedException 对 Average 使用已编译的 lambda 表达式时
NotSupportedException when using compiled lambda expression for Average
我试图回答 但失败了:
所以让我们采用原始查询:
var result = db.Employees.GroupBy(x => x.Region)
.Select(g => new { Region = g.Key,
Avg = g.Average(x => x.BaseSalary)});
工作正常。现在我们要动态地决定平均什么。我尝试为 Average
动态创建 lambda:
string property = "BaseSalary";
var parameter = Expression.Parameter(typeof(Employee));
var propAccess = Expression.PropertyOrField(parameter, property);
var expression = (Expression<Func<Employee,int?>>)Expression.Lambda(propAccess, parameter);
var lambda = expression.Compile();
并使用它:
var result = db.Employees.GroupBy(x => x.Region)
.Select(g => new { Region = g.Key,
Avg = g.Average(lambda)});
对于 Linq2Sql,这会导致 NotSupportedException
:
Für den Abfrageoperator "Average" wurde eine nicht unterstützte Überladung verwendet.
(我只有德文错误信息,它说 不支持 Average
的重载 ,如果你有英文版本,请随意编辑).
原题用的是Linq2Entities,报错
Internal .NET Framework Data Provider error 102
IntelliSense(或其他一些 IDE 功能)告诉我,在两个版本中,编译器选择 相同的重载 of Average
:
double? Enumerable.Average(this IEnumerable<Employee> source, Func<Employee, int?> selector);
然后我用 ExpressionVisitor
重新检查了我的 lambda
与 x => x.BaseSalary
.
完全相同的 表达式
所以:为什么它突然不再受支持了?
有趣:如果我不分组并简单地使用它,就没有这样的例外:
double? result = db.Employees.Average(lambda);
使用 YuvalShap's answer 我也尝试了 Avg = g.AsQueryable().Average(expression)
(使用表达式而不是 lambda),但结果相同。
你不应该编译 lambda。 EF 使用表达式树而不是编译代码,因此它可以将表达式转换为 SQL 而不是代码中的 运行。
没有编译错误,因为 Enumerable.Average
确实需要 Func<T, int?>
,因此使用了重载。但是当转换为 SQL 时,EF 不知道如何处理已编译的 lambda。
由于Average
在分组中,你不能将表达式传递给它,你必须将整个表达式构建到Select
。
由于这会创建非常混乱的代码,您可以创建一个自定义版本的 Select
,用您的自定义表达式替换表达式的一部分以获得平均值,因此至少 [=27] 的主要部分=] 可读:
public static class Helper
{
public static IQueryable<TResult> SelectWithReplace<T, TKey, TResult>(this IQueryable<IGrouping<TKey, T>> queryable, Expression<Func<IGrouping<TKey, T>, Func<T, int?>, TResult>> select, Expression<Func<T, int?>> replaceWith)
{
var paramToReplace = select.Parameters[1];
var newBody = new ReplaceVisitor(paramToReplace, replaceWith).Visit(select.Body);
var newSelect = Expression.Lambda<Func<IGrouping<TKey, T>, TResult>>(newBody, new[] { select.Parameters.First() });
return queryable.Select(newSelect);
}
public class ReplaceVisitor : ExpressionVisitor
{
private readonly ParameterExpression toReplace;
private readonly Expression replaceWith;
public ReplaceVisitor(ParameterExpression toReplace, Expression replaceWith)
{
this.toReplace = toReplace;
this.replaceWith = replaceWith;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if(node == toReplace)
{
return this.replaceWith;
}
return base.VisitParameter(node);
}
}
}
用法:
string property = "BaseSalary";
var parameter = Expression.Parameter(typeof(Employee));
var propAccess = Expression.PropertyOrField(parameter, property);
var expression = (Expression<Func<Employee, int?>>)Expression.Lambda(propAccess, parameter);
var result = db.Employees
.GroupBy(x => x.Region)
.SelectWithReplace((g, willReplace) => new
{
Region = g.Key,
Avg = g.Average(willReplace)
}, expression);
我试图回答
所以让我们采用原始查询:
var result = db.Employees.GroupBy(x => x.Region)
.Select(g => new { Region = g.Key,
Avg = g.Average(x => x.BaseSalary)});
工作正常。现在我们要动态地决定平均什么。我尝试为 Average
动态创建 lambda:
string property = "BaseSalary";
var parameter = Expression.Parameter(typeof(Employee));
var propAccess = Expression.PropertyOrField(parameter, property);
var expression = (Expression<Func<Employee,int?>>)Expression.Lambda(propAccess, parameter);
var lambda = expression.Compile();
并使用它:
var result = db.Employees.GroupBy(x => x.Region)
.Select(g => new { Region = g.Key,
Avg = g.Average(lambda)});
对于 Linq2Sql,这会导致 NotSupportedException
:
Für den Abfrageoperator "Average" wurde eine nicht unterstützte Überladung verwendet.
(我只有德文错误信息,它说 不支持 Average
的重载 ,如果你有英文版本,请随意编辑).
原题用的是Linq2Entities,报错
Internal .NET Framework Data Provider error 102
IntelliSense(或其他一些 IDE 功能)告诉我,在两个版本中,编译器选择 相同的重载 of Average
:
double? Enumerable.Average(this IEnumerable<Employee> source, Func<Employee, int?> selector);
然后我用 ExpressionVisitor
重新检查了我的 lambda
与 x => x.BaseSalary
.
所以:为什么它突然不再受支持了?
有趣:如果我不分组并简单地使用它,就没有这样的例外:
double? result = db.Employees.Average(lambda);
使用 YuvalShap's answer 我也尝试了 Avg = g.AsQueryable().Average(expression)
(使用表达式而不是 lambda),但结果相同。
你不应该编译 lambda。 EF 使用表达式树而不是编译代码,因此它可以将表达式转换为 SQL 而不是代码中的 运行。
没有编译错误,因为 Enumerable.Average
确实需要 Func<T, int?>
,因此使用了重载。但是当转换为 SQL 时,EF 不知道如何处理已编译的 lambda。
由于Average
在分组中,你不能将表达式传递给它,你必须将整个表达式构建到Select
。
由于这会创建非常混乱的代码,您可以创建一个自定义版本的 Select
,用您的自定义表达式替换表达式的一部分以获得平均值,因此至少 [=27] 的主要部分=] 可读:
public static class Helper
{
public static IQueryable<TResult> SelectWithReplace<T, TKey, TResult>(this IQueryable<IGrouping<TKey, T>> queryable, Expression<Func<IGrouping<TKey, T>, Func<T, int?>, TResult>> select, Expression<Func<T, int?>> replaceWith)
{
var paramToReplace = select.Parameters[1];
var newBody = new ReplaceVisitor(paramToReplace, replaceWith).Visit(select.Body);
var newSelect = Expression.Lambda<Func<IGrouping<TKey, T>, TResult>>(newBody, new[] { select.Parameters.First() });
return queryable.Select(newSelect);
}
public class ReplaceVisitor : ExpressionVisitor
{
private readonly ParameterExpression toReplace;
private readonly Expression replaceWith;
public ReplaceVisitor(ParameterExpression toReplace, Expression replaceWith)
{
this.toReplace = toReplace;
this.replaceWith = replaceWith;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if(node == toReplace)
{
return this.replaceWith;
}
return base.VisitParameter(node);
}
}
}
用法:
string property = "BaseSalary";
var parameter = Expression.Parameter(typeof(Employee));
var propAccess = Expression.PropertyOrField(parameter, property);
var expression = (Expression<Func<Employee, int?>>)Expression.Lambda(propAccess, parameter);
var result = db.Employees
.GroupBy(x => x.Region)
.SelectWithReplace((g, willReplace) => new
{
Region = g.Key,
Avg = g.Average(willReplace)
}, expression);