EF Core中identity resolution概念及跟踪/非跟踪查询规则解析
EF Core 两种查询行为的实体返回规则解释
核心差异来自查询是否启用变更跟踪,以及配套的身份解析机制:
- 跟踪查询的行为逻辑
跟踪查询默认会走EF Core内置的变更追踪器,执行身份解析逻辑。简单说就是变更追踪器会给已经查出来的实体做缓存,缓存键就是实体的主键值。
所谓「查询结果多次包含同一实体时返回相同实例」,就是指哪怕你的联表查询因为一对多、多对多关联,导致同一个主键的实体在结果集里重复出现了N次,EF Core都不会重复创建新的实体对象,每次都会直接返回变更追踪器里缓存的那同一个实例。你用引用相等判断,这些重复出现的实体全指向内存里的同一个地址,改其中任意一个的属性,所有地方拿到的这个实体都会同步变化——毕竟本来就是同一个对象。
这个设计是为了保证状态跟踪的一致性,避免同一个主键对应多个对象实例,导致保存变更时出现状态冲突、覆盖的问题。 - 非跟踪查询的行为逻辑
非跟踪查询从设计上就不会启用变更追踪器,自然也不会做身份解析和实体缓存。哪怕同一个主键的实体在结果集里重复出现,EF Core每读到一条对应记录,就会重新实例化一个全新的实体对象返回。这些对象虽然主键、所有属性值可能完全一模一样,但在内存里是完全独立的实例,你改其中一个的属性,其他重复返回的同主键实体不会有任何变化。
你可以跑下面这段测试代码直观感受差异:
// 跟踪查询场景 using var db = new TestDbContext(); // 联表加载一对多导航属性,会导致主表实体在扁平化结果中重复出现 var trackQueryResult = db.Blogs.Include(b => b.Posts).ToList(); // 取两个Id相同的Blog条目 var blog1 = trackQueryResult[0]; var blog2 = trackQueryResult.Last(b => b.Id == blog1.Id); // 判断内存引用是否相等 Console.WriteLine(object.ReferenceEquals(blog1, blog2)); // 输出:True,两个变量指向同一个实体实例 // 非跟踪查询场景 using var db2 = new TestDbContext(); var noTrackQueryResult = db.Blogs.AsNoTracking().Include(b => b.Posts).ToList(); var blogA = noTrackQueryResult[0]; var blogB = noTrackQueryResult.Last(b => b.Id == blogA.Id); Console.WriteLine(object.ReferenceEquals(blogA, blogB)); // 输出:False,两个变量是完全独立的不同实例
开发踩坑提示:非跟踪查询因为不需要做缓存和身份解析,只读场景下执行效率更高,但如果要手动附加实体做更新操作,要注意避免同主键重复实例被重复跟踪引发报错。
内容的提问来源于stack exchange,提问作者variable
相关产品推荐
相关产品推荐

