NotSupportedException when using compiled lambda expression for Average

我的未来我决定 提交于 2019-12-04 19:03:41

You should not compile the lambda. EF works with expression trees not with the compiled code, so that it can transform the Expression to SQL rather then running it in code.

There is no compilation error because there is an Enumerable.Average which does take a Func<T, int?> so that overload is used. But when converting to SQL EF does not know what to do with the compiled lambda.

Since Average is on the grouping, you can't pass the expression to it, you have to build up the entire expression to Select.

Since that can create very obfuscated code, you can create a custom version of Select that replaces a portion of the expression with your custom expression for average, so at least the main part of the select is readable:

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);
        }
    }
}

Usage:

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);
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!