将已编译的lambda表达式用于Average时的NotSupportedException

我试图回答这个问题,但失败了:

那么我们来看看原始查询:

var result = db.Employees.GroupBy(x => x.Region) .Select(g => new { Region = g.Key, Avg = g.Average(x => x.BaseSalary)}); 

工作良好。 现在我们想要动态决定平均值。 我尝试动态创建lambda的lambda:

 string property = "BaseSalary"; var parameter = Expression.Parameter(typeof(Employee)); var propAccess = Expression.PropertyOrField(parameter, property); var expression = (Expression<Func>)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ürdenAbfrageoperator“Average”wurde einenichtunterstützteÜberladungverwendet。

(我只有德语错误消息,它表示不支持使用的Average重载,如果你有英文版,可以随意编辑)。

最初的问题使用了Linq2Entities并得到了错误

内部.NET Framework数据提供程序错误102

IntelliSense(或其他一些IDEfunction)告诉我,在两个版本中,编译器选择相同Average 过载

 double? Enumerable.Average(this IEnumerable source, Func selector); 

我用一个ExpressionVisitor重新检查我的lambdax => x.BaseSalary

那么: 为什么它突然不再受支持了?


有趣的是:如果我不分组并使用它,就没有这样的例外:

 double? result = db.Employees.Average(lambda); 

在YuvalShap的回答中,我也尝试了Avg = g.AsQueryable().Average(expression) (使用表达式而不是lambda),但结果相同。

你不应该编译lambda。 EF使用表达式树而不是编译代码,因此它可以将Expression转换为SQL,而不是在代码中运行它。

没有编译错误,因为有一个Enumerable.Average确实采用Func以便使用重载。 但是当转换为SQL时,EF不知道如何处理已编译的lambda。

由于Average在分组上,因此无法将表达式传递给它,您必须将整个表达式构建为“ Select

由于这可以创建非常混淆的代码,因此您可以创建一个自定义版本的Select ,它将表达式的一部分替换为您的自定义表达式的平均值,因此至少select的主要部分是可读的:

 public static class Helper { public static IQueryable SelectWithReplace(this IQueryable> queryable, Expression, Func, TResult>> select, Expression> replaceWith) { var paramToReplace = select.Parameters[1]; var newBody = new ReplaceVisitor(paramToReplace, replaceWith).Visit(select.Body); var newSelect = Expression.Lambda, 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>)Expression.Lambda(propAccess, parameter); var result = db.Employees .GroupBy(x => x.Region) .SelectWithReplace((g, willReplace) => new { Region = g.Key, Avg = g.Average(willReplace) }, expression);