我试图回答this question,但失败了:
让我们来看看原始查询:
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ürdenAbfrageoperator“Average”wurde einenichtunterstützteÜberladungverwendet。
(我只有德语错误消息,它表示 Average
的使用过载不受支持,如果你有英文版,请随时编辑。)
原始问题使用Linq2Entities并收到错误
内部.NET Framework数据提供程序错误102
IntelliSense(或其他一些IDE功能)告诉我,在两个版本中,编译器选择{strong> 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),但结果相同。
答案 0 :(得分:4)
你不应该编译lambda。 EF使用表达式树而不是编译代码,因此它可以将Expression转换为SQL,而不是在代码中运行它。
没有编译错误,因为Enumerable.Average
确实占用了Func<T, int?>
,因此使用了重载。但是当转换为SQL时,EF不知道如何处理已编译的lambda。
由于Average
在分组上,您无法将表达式传递给它,您必须将整个表达式构建为Select
。
由于这可以创建非常混淆的代码,您可以创建Select
的自定义版本,用自定义表达式替换表达式的一部分以进行平均,因此至少select的主要部分是可读的:< / p>
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);