LINQ表达式无法翻译。请将查询重写为可翻译的形式,或切换到客户端评估EF Core 3.1。

11

我已经苦苦挣扎了四天,但一点进展都没有。 在更新到EF Core 3.1之前,这个查询运行得非常好:

var equipments = await this.DbContext.ServContrObjStructEquipment
            .AsNoTracking()
            .Where(e => e.ServContrObjStruct.ServContrObjStructParent.ServContrObjStructParent.Objectuuid == sectionGuid)
            .Where(e => e.ServContrObjStructPlanEquipment.Select(x => x.PkServContrObjStructPlanEquipment).Contains(
                e.ServContrObjStructPlanEquipment.OrderByDescending(x => x.ServContrObjStructPlanVers.ActiveUntil ?? DateTime.MaxValue).ThenByDescending(x => x.ServContrObjStructPlanVers.ActiveFrom).ThenByDescending(x => x.ActiveFrom).Select(x => x.PkServContrObjStructPlanEquipment).FirstOrDefault()
                ))

现在它抛出一个异常,说:
LINQ表达式'DbSet.Where(s3 => s3.RecStatus == 1).Where(s3 => EF.Property, "PkServContrObjStructEquipment") != null && EF.Property, "PkServContrObjStructEquipment") == EF.Property).Select(s3 => s3.PkServContrObjStructPlanEquipment).Contains((MaterializeCollectionNavigation(navigation: Navigation: ServContrObjStructEquipment.ServContrObjStructPlanEquipment, subquery: DbSet.Where(s4 => s4.RecStatus == 1).Where(i => EF.Property, "PkServContrObjStructEquipment") != null && EF.Property, "PkServContrObjStructEquipment") == EF.Property)).AsQueryable().OrderByDescending(x => x.ServContrObjStructPlanVers.ActiveUntil ?? 12/31/9999 11:59:59 PM).ThenByDescending(x => x.ServContrObjStructPlanVers.ActiveFrom).ThenByDescending(x => x.ActiveFrom).Select(x => x.PkServContrObjStructPlanEquipment).FirstOrDefault())'无法翻译。请重新编写查询,以便可以翻译,或者通过插入对AsEnumerable()、AsAsyncEnumerable()、ToList()或ToListAsync()的调用来明确切换到客户端评估。有关更多信息,请参见https://go.microsoft.com/fwlink/?linkid=2101038
我知道EF Core 3.0-3.1有重大变化,现在客户端评估是在顶层的Select()上执行的。虽然我宁愿避免这样做,但我尝试调用所有ToList()AsEnumerable()等来使其工作,但也没有帮助:查询是复杂和多级的,因此在任何时候调用ToList()要么会破坏它,要么不会获取相关记录(可能是由于惰性加载),而这些记录随后需要进一步执行查询。
我还尝试将查询拆分成单独的查询,以查看那里发生了什么,像这样:
var intermEquipments = await this.DbContext.ServContrObjStructEquipment
                .AsNoTracking()
                .Include(e => e.ServContrObjStructPlanEquipment)
             .Where(e=>e.ServContrObjStruct.ServContrObjStructParent.ServContrObjStructParent.Objectuuid == sectionGuid)
                .ToListAsync();

            var intermEquipments1 = await this.DbContext.ServContrObjStructEquipment
                .AsNoTracking()
                .Include(e => e.ServContrObjStructPlanEquipment)
                .Where(e => e.ServContrObjStruct.ServContrObjStructParent.ServContrObjStructParent.Objectuuid == sectionGuid)
                .Select(e => e.ServContrObjStructPlanEquipment.Select(x => x.PkServContrObjStructPlanEquipment))
                .ToListAsync();

            var intermEquipments2 = this.DbContext.ServContrObjStructEquipment
                .AsNoTracking()
                .Include(e => e.ServContrObjStructPlanEquipment)
                .Where(e => e.ServContrObjStruct.ServContrObjStructParent.ServContrObjStructParent.Objectuuid == sectionGuid)
                .Select(e => e.ServContrObjStructPlanEquipment
                    .OrderByDescending(x => x.ServContrObjStructPlanVers.ActiveUntil ?? DateTime.MaxValue)
                                .ThenByDescending(x => x.ServContrObjStructPlanVers.ActiveFrom)
                                    .ThenByDescending(x => x.ActiveFrom)
                                        .Select(x => x.PkServContrObjStructPlanEquipment).ToList());

但是它要么抛出一个异常,说

在Include中使用的Lambda表达式无效

要么NullReference(因为一些相关属性没有加载,如我上面提到的),或者根本不显示记录。

我熟悉EntityFramework,虽然我不是专家。有没有办法重写这个查询,使它可以被翻译或者做任何事情使它工作? 谢谢!


为什么在拆分查询中需要使用Include?您有这个NextreeContext.ServContrObjStructEquipment和.Include(e => e.ServContrObjStructPlanEquipment)。由于您没有收到编译器错误,因此编译器已经识别了ServContrObjStructEquipment对象,您不需要使用include。 - jdweng
看起来问题出现在两个“Where”表达式中的一个。很可能是第二个,但为了确保,首先注释掉它们两个,然后取消注释其中一个等等,直到你得到异常。 - Ivan Stoev
这个结构似乎有点奇怪。你最好把那些where语句改成连接语句,不是更好吗? - Mike-314
@IvanStoev 问题在于 Contains 表达式,我已经确认过了。 - Yurii Proniuk
@Mike-314,你说的将它们转换为连接,具体是什么意思? - Yurii Proniuk
1个回答

4
这似乎是EF Core中的一个错误或某种突破性变化(惊喜惊喜)。起初,我怀疑使用DateTime.MaxValue因为EF Core过去曾经有过这样的问题,但不是那个问题。由于某种原因,它拒绝在Contains中进行OrderBy子选择。EF Core 3与2.2.6相比的一个突破性变化是,Core 2会自动切换到客户端表达式,而Core会抛出异常,但是我能够通过分析器确认像这样的查询已成功转换为SQL与Core 2.2.6,但在3.1中不会转换导致错误。此时,您可以向EF Core团队提出错误报告。
我能够使用更简单的查询重现此问题:
var results = context.Parents.Where(x => x.Children.Select(c => c.ChildId)
   .Contains(x.Children.OrderByDescending(c => c.BirthDate).Select(c => c.ChildId).FirstOrDefault()))
   .ToList();

在2.2.6版本中它成功编译为SQL。但在3.1版本中会抛出异常。

确定此查询的实际意图可能会更有益,因为我已经阅读了三遍,还是不太理解。:)

您正在选择其Struct.Paremt.Parent.ObjectID =部分ID且其plan Equipment包含的ServerEquipment,从我所看到的内容来看,该集合中最早的Equipment记录。那个Contains检查没有任何外部条件,这样做好像没有意义?我的意思是,简化后看起来像是.Where(x => x.Children.Select(c => c.ChildId).Contains(x.Children.OrderByDescending(c2 => c2.BirthDate).Select(c2 => c2.ChildId).FirstOrDefault()),这几乎是浪费数据库时间的花哨方式。即过滤Children包含最年长的孩子。(当然啦.)所以我只能推断我在那里错过了什么,但是六个月后我真的不想尝试去学习这种实体结构和查询.. :)

一个来自自身子项集合的包含检查而没有任何外部条件是没有意义的。通常,这些类型的查询将查看子集合以查看它们是否包含可由上层Where子句内的Any()类型检查满足的某些匹配条件。重新审视要过滤的内容可能有助于简化此查询并避免出现此问题。


你好Steve!感谢你回答我的问题!在我之前已经有人写过这个查询,我也遇到了一些支持上的问题。据我所知,有这样一个层次结构:结构 -> 建筑物 -> 区域 -> 房间区域,所有这些实体都在数据库中的同一张表中。因此,这就需要进行自连接,这可能会在这个查询中发生。 它必须选择特定结构(确切地说是房间区域)的结构平面图的最新版本以及放置在该平面图上的设备。 - Yurii Proniuk
哦,是啊,开发人员提出这样的数据结构真是太棒了。 :) 试图在 StackOverflow 上解决如此复杂的结构确实会带来困难的问题。很明显,StructurePlans 是历史性的,你想要获取最新版本。那么设备呢?这些结构元素本身是否也是历史性的?例如,如果你找到了特定部分的房间面积,那么这个房间面积是否有历史版本,或者该房间区域中的设备是否有历史版本,比如按照计划? - Steve Py

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接