EF Core 2.1 RC1:更新子模型后如何高效获取父导航属性?
解决EF Core 2.1更新子实体后无法加载父导航属性的问题
这个问题的核心原因是EF Core的上下文跟踪机制:你调用Update(childInstance)后,这个子实体已经被上下文跟踪了。当你后续执行SingleAsync查询时,EF Core会直接返回内存中已存在的那个实例(也就是你之前更新的childInstance),而不会去数据库重新查询——哪怕你加了Include(p => p.Parent),内存里的实例Parent还是null,自然不会被填充。
下面给你几个高效的解决方案,按推荐程度排序:
1. 直接加载父导航属性(最推荐,性能最优)
不需要重新查询整个子实体,直接通过上下文的Entry API加载父导航属性:
this.context.Update(childInstance); await this.context.SaveChangesAsync(); // 加载父导航属性(Reference用于单个导航属性,集合导航用Collection) await this.context.Entry(childInstance) .Reference(p => p.Parent) .LoadAsync(); // 现在childInstance.Parent就已经被填充,直接使用即可
这种方法只需要额外执行一次查询加载父实体,不需要重新获取子实体,性能最高。
2. 解除原实例的跟踪状态
如果你需要从数据库获取一个全新的、包含父属性的子实体实例,可以先把原来的childInstance从上下文跟踪中移除,这样后续的查询就会去数据库拉取最新数据并应用Include:
this.context.Update(childInstance); await this.context.SaveChangesAsync(); // 解除上下文对原实例的跟踪 this.context.Entry(childInstance).State = EntityState.Detached; // 现在查询会从数据库获取新实例,Include会生效 var child = await this.context.Children .Include(p => p.Parent) .SingleAsync(p => p.Id == childInstance.Id);
3. 使用AsNoTracking查询
另一种绕开上下文跟踪的方法是在查询时使用AsNoTracking(),这样EF Core会忽略内存中已有的实例,直接从数据库查询并返回新的实例,同时应用Include:
this.context.Update(childInstance); await this.context.SaveChangesAsync(); // AsNoTracking()让查询跳过上下文的跟踪缓存 var child = await this.context.Children .AsNoTracking() .Include(p => p.Parent) .SingleAsync(p => p.Id == childInstance.Id);
补充说明
- EF Core 2.1中
Reference.LoadAsync()和Collection.LoadAsync()是手动加载导航属性的官方API,如果你没启用延迟加载(默认关闭),这就是最直接的方式。 - 若你频繁遇到这类场景,也可以考虑启用延迟加载(需安装
Microsoft.EntityFrameworkCore.Proxies包并配置UseLazyLoadingProxies()),但要注意延迟加载可能引发N+1查询问题,需结合业务场景权衡。
内容的提问来源于stack exchange,提问作者Ritmo2k
相关产品推荐
相关产品推荐

