将已编译的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
重新检查我的lambda
与x => 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);