【问题标题】:Entityframework Core 3 linq expression could not be translatedEntityframework Core 3 linq 表达式无法翻译
【发布时间】:2020-04-11 13:33:37
【问题描述】:

我刚刚升级到 EF 3,我的一个查询曾经可以工作,现在却给出了异常

   ProductionRecords = _context.ProductionRecords
          .Where(r => r.DataCriacao.Date == DateTime.Now.Date)
            .Select(pr => new ProductionRecordViewModel
            {
                Id = pr.Id,
                Operador = pr.Operador,
                DataCriacao = pr.DataCriacao,
                Celula = pr.Celula.Name,
                Turno = pr.Turno.Name,
                TotalPecasSemDefeito = pr.ReferenceRecords.Sum(c => c.Quantity),
                TotalPecasComDefeito = pr.DefectRecords.Sum(c => c.Quantidade),
                TotalTempoParado = pr.StopRecords.Sum(c => Convert.ToInt32(c.Duration.TotalMinutes)),
            })
          .AsNoTracking()
          .ToList();

当我尝试将集合与时间跨度和持续时间相加时会发生异常......

我现在该怎么处理?

这里是例外

InvalidOperationException:LINQ 表达式 '(EntityShaperExpression:EntityType:StopRecord ValueBufferExpression: (ProjectionBindingExpression: EmptyProjectionMember) IsNullable: False ).Duration.TotalMinutes' 无法翻译。要么重写查询的形式,可以 被翻译,或通过插入显式切换到客户端评估 调用 AsEnumerable()、AsAsyncEnumerable()、ToList() 或 ToListAsync()。见https://go.microsoft.com/fwlink/?linkid=2101038 更多信息。

【问题讨论】:

  • 您能否更新问题以包含例外情况
  • 任何 .net 方法或类都不能转换为查询。您的 convert.toint() 和 Duration.totalminutes 可能是您的问题。如果您仍然想要这些,请在您的选择之前添加 .tolist()。
  • TotalTempoParado = pr.StopRecords pr.StopRecords.Sum(c => Convert.ToInt32(c.Duration.TotalMinutes)) 是错字吗?
  • 您应该将此问题发布到 EF Core GitHub 以了解他们处理此类问题的愿景。显然,按照建议切换到客户端评估是不可能的,并且添加急切/延迟加载会使其效率非常低。
  • @ivanstoev 客户端评估是可能的,如果您调用 asnotracking,然后列出,然后过滤这些结果。

标签: c# entity-framework-core


【解决方案1】:

与其尝试教 EF Core 如何求和时间跨度,不如将计算列添加到数据库中?

        public TimeSpan Duration { get; set; }
        public int Minutes { get; }


        entity.Property(e => e.Minutes)
            .HasComputedColumnSql("DATEDIFF(MINUTE, 0, Duration)");

【讨论】:

  • 我喜欢这种方法,我可以保留我的查询并只使用性能更高的计算列。我没有提出正确的问题,因为我已经接受了。
【解决方案2】:

EF3 发生了重大变化,除非在查询链的最后(您的 Convert.ToInt32(c.Duration.TotalMinutes) 可能依赖它),否则不会自动恢复到客户端评估。

尝试像这样重写您的查询:

 ProductionRecords = _context.ProductionRecords
      .Where(r => r.DataCriacao.Date == DateTime.Now.Date)
        .AsNoTracking()
        .AsEnumerable()
        .Select(pr => new ProductionRecordViewModel
        {
            Id = pr.Id,
            Operador = pr.Operador,
            DataCriacao = pr.DataCriacao,
            Celula = pr.Celula.Name,
            Turno = pr.Turno.Name,
            TotalPecasSemDefeito = pr.ReferenceRecords.Sum(c => c.Quantity),
            TotalPecasComDefeito = pr.DefectRecords.Sum(c => c.Quantidade),
            TotalTempoParado = pr.StopRecords pr.StopRecords.Sum(c => Convert.ToInt32(c.Duration.TotalMinutes)),
        })
      .ToList();

UPD 正如 cmets 中正确指出的那样 - 这基本上会将 .Select 评估推迟到客户端。这可能会导致性能问题。这种行为很可能是最初对 EF Core 3 进行此更改的原因。

我没有足够的细节来向您推荐合适的解决方案,但您似乎无法真正摆脱在所有结果中加载StopRecords。这就是编写自定义方法翻译器可以帮助您的地方。请参阅我的other answer 了解如何做到这一点。我快速检查了 EF Core 3 源代码,似乎 IMethodCallTranslator 仍然存在。这意味着您很有可能构建一个自定义函数,将日期转换为 SQL 中的 TotalMinutes。

【讨论】:

  • 还要注意 AsNoTracking 必须在 AsEnumerable 之后
  • 值得注意的是,这可能会导致对 Select 子句中汇总的三个表中的每一个表进行额外的查询。可能 N+1 次
  • @devNull 或者 NRE 或 0 总和,以防延迟加载未启用。 “答案”基本上重复了异常消息和建议的客户端评估解决方案,总体上不好。
  • 在开放使用 IEnumerable 数据进程时要小心,因为它在应用程序级别进行处理,而资源成本很高。
  • @IvanStoev 公平点,我添加了更多上下文和正确解决根本原因的建议方法。希望这为 OP 和社区增加了足够的价值
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-11
  • 2021-12-28
  • 1970-01-01
  • 2022-12-03
  • 2020-09-10
相关资源
最近更新 更多