.Net Core 3.1 启用懒加载时刚保存的Address查询User导航为null
问题背景
业务流程需要在完成地址实体的数据库持久化后,给用户发送成功通知,通知内容依赖Address关联的User属性。当前遇到的异常是:刚保存完成的Address,立即通过仓储方法查询回来时,User导航属性返回null,但查询数据库中其他已存在的历史Address时,User导航属性可正常访问,且全局已开启懒加载。
附原始代码片段:
实体定义:
public class User{ public int Id {get; set;} public ICollection<Address> Users {get; set;} } public class Address{ public int Id {get; set;} public string Name {get; set;} public int UserId {get; set;} public virtual User User {get; set;} }
复现代码:
Address address = new Address(){name = "abc"}; unitOfWork.Repository.Add(address) unitOfWork.Commit() var savedAddress = unitOfWork.Repository.GetById(address.Id); // 此处savedAddress.User 为 null
问题根因
- EF系列ORM默认开启本地变更跟踪机制:刚调用Add新增的实体,在Commit完成后仍然会被上下文的本地缓存持有。此时调用
GetById查询刚保存的Address,ORM会直接返回本地缓存里匹配ID的实体,根本不会真的发SQL去数据库查询。 - 懒加载有生效前提,要求实体是ORM动态生成的代理类实例。手动new出来的Address是普通类实例,并非动态代理类,哪怕全局开了懒加载,访问
User属性时也触发不了自动查询逻辑,自然返回null。 - 查询历史Address时,本地缓存没有对应的跟踪条目,ORM会真正执行数据库查询,加载出来的实体是带懒加载代理的,因此访问
User属性就完全正常。 - 原实体定义存在语义瑕疵:
User类中的地址集合属性命名为Users不符合命名规范,建议调整为Addresses,减少后续维护的歧义。
可落地的解决方式
- 强制从数据库重新查询,绕开本地缓存命中。在Commit完成后,先把原来手动new的Address实例从上下文的跟踪列表里剥离,再调用GetById,此时ORM找不到本地缓存的条目,就会真的查库加载带懒加载代理的实体,
User属性就能正常访问。
参考操作代码:unitOfWork.Commit(); // 把原实体从上下文跟踪中分离 unitOfWork.Context.Entry(address).State = EntityState.Detached; var savedAddress = unitOfWork.Repository.GetById(address.Id); - 查询时显式关联加载需要的User属性,不依赖懒加载逻辑。直接在查询Address的时候指定Include加载User,不管代理和缓存逻辑,直接把需要的关联数据查回来,方案最稳妥。
参考代码:var savedAddress = unitOfWork.Repository .GetQueryable<Address>() .Include(a => a.User) .First(a => a.Id == address.Id); - 从业务流程上避开这个问题。本来生成通知需要的用户属性,在发起地址保存请求时就已经能从登录上下文获取,直接在构建通知的时候用内存里的用户信息就行,不需要等保存完从Address的导航属性反向取值,从根上不会碰到这个缓存和懒加载的问题。
额外提醒:示例代码里新增Address的时候只给Name赋值了,没有设置UserId外键值,实际业务里一定要确保外键正确赋值,不然数据库层面根本关联不到对应用户,就算解决了缓存问题也取不到正确的User数据。
内容的提问来源于stack exchange,提问作者bippan
相关产品推荐
相关产品推荐

