LinqKit 无法将类型 'System.Linq.Expressions.InstanceMethodCallExpression2' 的对象转换为类型 'System.Linq.Expressions.LambdaExpression'

LinqKit Unable to cast object of type 'System.Linq.Expressions.InstanceMethodCallExpression2' to type 'System.Linq.Expressions.LambdaExpression'

我对 Expressions 有疑问。我有这个 lambda LINQ 查询 Entity Framework Core LINQ Provider:

IQueryable<ProcessValueBase> valuesSubquery;
switch (req.Period)
            {
                case TimePeriodType.Current:
                    valuesSubquery =  dbContext.ProcessValues;
                    break;
                case TimePeriodType.Day:
                case TimePeriodType.Week:
                case TimePeriodType.Month:
                    valuesSubquery = dbContext.NormalizedLogValues;
                    break;
                default:
                    valuesSubquery = dbContext.ProcessValues;
                    break;
            }
var res = dbContext.Rooms.Select(r => new
            {
                RoomId = r.Id,
                ZoneId = r.ZoneId,
                IdealSetpoint = r.Group.Setpoints.First(sp => sp.ClimaticZoneId == dbContext.ClimaticZonesLogs.OrderByDescending(cz => cz.Timestamp).First().ClimaticZoneId).Setpoint,
                Devices = r.Devices
                .Select(rd => rd.Device)
                .Select(d => new
                {
                    Id = d.Id,
                    Name = d.Name,
                    //Setpoint = GetQuery(rd.Device.Id).Average(t=>t.Value)
                    Setpoint = valuesSubquery.Where(GetQuery(req.Period, d)).Average(t => t.Value)
                })
            }
            ).ToList();

然后我有一个动态处理谓词的函数:

private  Expression<Func<ProcessValueBase,bool>> GetQuery(string period, DeviceModel device)
        {
            var predicate = PredicateBuilder.New<ProcessValueBase>();
            predicate = predicate.And(v => v.TagSettings.DeviceId == device.Id);
            predicate = predicate.And(v => v.TagSettings.TagTypeId == GetSetpointTagTypeId(device.DeviceTypeId));
            predicate = predicate.And(v => v.ClimaticZoneId == dbContext.ClimaticZonesLogs.OrderByDescending(cz => cz.Timestamp).First().ClimaticZoneId);

            var utcTime = DateTime.Now.ToUniversalTime();
            switch (period)
            {
                case TimePeriodType.Day:
                    var startOfDay = utcTime.StartOfDay();
                    predicate = predicate.And(v => v.Timestamp >= startOfDay && v.Timestamp < startOfDay.AddDays(1));
                    break;
                case TimePeriodType.Week:
                    var startOfWeek = utcTime.FirstDayOfWeek();
                    predicate = predicate.And(v => v.Timestamp >= startOfWeek && v.Timestamp < startOfWeek.AddDays(7));
                    break;
                case TimePeriodType.Month:
                    var startOfMonth = utcTime.FirstDayOfMonth();
                    predicate = predicate.And(v => v.Timestamp >= startOfMonth && v.Timestamp < startOfMonth.AddMonths(1));
                    break;
                default:
                    break;
            }
            return predicate;
        }

我得到的错误如下:

消息是:

Unable to cast object of type 'System.Linq.Expressions.InstanceMethodCallExpression2' to type 'System.Linq.Expressions.LambdaExpression'.

谁能指出我做错了什么?

提前致谢,

朱利安

编辑: 作为对 Svyatoslav Danyliv 回答的回答。它给我的例外是:

The LINQ expression 'DbSet<NormalizedLogValueModel>()
    .Where(n => (v, device) => v.TagSettings.DeviceId == device.Id && v.TagSettings.TagTypeId == SetpointSideViewHandler.GetSetpointTagTypeId(device.DeviceTypeId) && v.ClimaticZoneId == DbSet<ClimaticZoneLogModel>()
        .OrderByDescending(c0 => c0.Timestamp)
        .Select(c0 => c0.ClimaticZoneId)
        .First() && v.Timestamp >= __startOfDay_0 && v.Timestamp < __AddDays_1
        .Invoke(
            arg1: n, 
            arg2: EntityShaperExpression: 
                EntityType: DeviceModel
                ValueBufferExpression: 
                    ProjectionBindingExpression: Inner
                IsNullable: True
        ))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.

GetQuery(req.Period).Compile()(p,d):

The LINQ expression 'DbSet<NormalizedLogValueModel>()
    .Where(n => Invoke(__Compile_0, n, [RelationalEntityShaperExpression])
    )' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.

好吧,答案很复杂,但根据我的经验,这只是一种方法:

用法:

var res = dbContext.Rooms
    .Select(r => new
    {
        RoomId = r.Id,
        ZoneId = r.ZoneId,
        IdealSetpoint = r.Group.Setpoints.First(sp => sp.ClimaticZoneId == dbContext.ClimaticZonesLogs.OrderByDescending(cz => cz.Timestamp).First().ClimaticZoneId).Setpoint,
        Devices = r.Devices
        .Select(rd => rd.Device)
        .Select(d => new
        {
            Id = d.Id,
            Name = d.Name,
            Setpoint = valuesSubquery.Where(p => GetQuery(req.Period).Invoke(dbContext, p, d)).Average(t => t.Value)
        })
    }
    ).ToList();

新功能(可能是编译错误,未测试):

private static Expression<Func<DBIContext, ProcessValueBase, DeviceModel, bool>> GetQuery(string period)
{
    Expression<Func<DBIContext, ProcessValueBase, DeviceModel, bool>> predicateTemplate = (dbContext, v, device) =>
        v.TagSettings.DeviceId == device.Id &&
        v.TagSettings.TagTypeId == GetSetpointTagTypeId(device.DeviceTypeId) &&
        v.ClimaticZoneId == dbContext.ClimaticZonesLogs.OrderByDescending(cz => cz.Timestamp).First().ClimaticZoneId;

    var predicate = PredicateBuilder.New<ProcessValueBase>();

    var utcTime = DateTime.Now.ToUniversalTime();
    switch (period)
    {
        case TimePeriodType.Day:
            var startOfDay = utcTime.StartOfDay();
            predicate = predicate.And(v => v.Timestamp >= startOfDay && v.Timestamp < startOfDay.AddDays(1));
            break;
        case TimePeriodType.Week:
            var startOfWeek = utcTime.FirstDayOfWeek();
            predicate = predicate.And(v => v.Timestamp >= startOfWeek && v.Timestamp < startOfWeek.AddDays(7));
            break;
        case TimePeriodType.Month:
            var startOfMonth = utcTime.FirstDayOfMonth();
            predicate = predicate.And(v => v.Timestamp >= startOfMonth && v.Timestamp < startOfMonth.AddMonths(1));
            break;
        default:
            break;
    }

    Expression<Func<ProcessValueBase, bool>> lambda = predicate;

    var newBody = predicateTemplate.Body;
    newBody = Expression.AndAlso(newBody, ExpressionReplacer.GetBody(lambda, predicateTemplate.Parameters[1]));

    return Expression.Lambda<Func<ProcessValueBase, DeviceModel, bool>>(newBody, predicateTemplate.Parameters);
}

[Expandable(nameof(GetSetpointTagTypeIdImpl))]
private static long GetSetpointTagTypeId(long deviceTypeId)
{
    throw new NotImplementedException();
}

private static Expression<Func<long, long>> GetSetpointTagTypeIdImpl()
{
    return deviceTypeId => deviceTypeId == 180_000 ? 180_002
        : deviceTypeId == 190_000 ? 190_002 : 0;
}

和帮手class:

public class ExpressionReplacer : ExpressionVisitor
{
    readonly IDictionary<Expression, Expression> _replaceMap;

    public ExpressionReplacer(IDictionary<Expression, Expression> replaceMap)
    {
        _replaceMap = replaceMap ?? throw new ArgumentNullException(nameof(replaceMap));
    }

    public override Expression Visit(Expression exp)
    {
        if (exp != null && _replaceMap.TryGetValue(exp, out var replacement))
            return replacement;
        return base.Visit(exp);
    }

    public static Expression Replace(Expression expr, Expression toReplace, Expression toExpr)
    {
        return new ExpressionReplacer(new Dictionary<Expression, Expression> {{toReplace, toExpr}}).Visit(expr);
    }

    public static Expression Replace(Expression expr, IDictionary<Expression, Expression> replaceMap)
    {
        return new ExpressionReplacer(replaceMap).Visit(expr);
    }
    
    public static Expression GetBody(LambdaExpression lambda, params Expression[] toReplace)
    {
        if (lambda.Parameters.Count != toReplace.Length)
            throw new InvalidOperationException();

        return new ExpressionReplacer(Enumerable.Range(0, lambda.Parameters.Count)
            .ToDictionary(i => (Expression) lambda.Parameters[i], i => toReplace[i])).Visit(lambda.Body);
    }
}