EF Core 2.0中基于子实体列表的计算属性需两次SaveChanges()问题
EF Core中
LastChildrenName需两次SaveChanges()才更新的原因与解决办法 问题原因
我猜你的LastChildrenName是在Children集合添加元素时自动更新的(比如用了集合的CollectionChanged事件或者属性变更通知)?问题的核心在于EF Core的变更追踪状态标记时机:
- 当你调用
DbContext.Set<Parent>().Update(parent)时,EF会立即把Parent实体标记为Modified,同时将新增的Children标记为Added。这时候EF已经记录了Parent所有属性的当前值作为"原始状态"。 - 而你添加子实体后更新
LastChildrenName的操作,是在调用Update之后发生的(或者说EF标记状态时,这个属性还没更新到最新值)。EF的变更追踪不会自动检测到这次内存中的属性变更——它只认标记状态时的属性值和后续的显式变更通知。 - 首次
SaveChanges()时,EF只会处理它已经追踪到的变更:新增3条Children数据,但不会更新Parent的LastChildrenName,因为这个属性的变更不在它的待提交列表里。 - 第一次提交后,EF会同步实体的数据库状态,这时候内存里的
LastChildrenName和数据库值不一致,第二次SaveChanges()时EF才会检测到这个差异并执行更新。
解决方案
这里给你三个可行的方案,按需选择:
方案一:调整顺序,先确保属性更新再标记状态
最直接的方式就是让LastChildrenName在EF标记Parent为Modified之前就变成最新值:
parent.Children.Add(new Children(){ Name = "FirstChild" }); parent.Children.Add(new Children(){ Name = "SecondChild" }); parent.Children.Add(new Children(){ Name = "ThirdChild" }); // 手动触发LastChildrenName的更新(如果你的自动逻辑没触发的话) parent.LastChildrenName = parent.Children.Last().Name; // 现在再让EF标记实体状态 DbContext.Set<Parent>().Update(parent); DbContext.SaveChanges(); // 一次提交就搞定
方案二:手动通知EF属性已变更
如果你的LastChildrenName是通过集合变更自动更新的(比如用ObservableCollection监听添加事件),可以在更新后手动告诉EF这个属性需要被提交:
parent.Children.Add(new Children(){ Name = "FirstChild" }); parent.Children.Add(new Children(){ Name = "SecondChild" }); parent.Children.Add(new Children(){ Name = "ThirdChild" }); DbContext.Set<Parent>().Update(parent); // 显式标记LastChildrenName为已修改 DbContext.Entry(parent).Property(p => p.LastChildrenName).IsModified = true; DbContext.SaveChanges(); // 一次提交生效
方案三:改用计算属性(推荐从根源解决)
如果LastChildrenName本质就是Children集合最后一个元素的名称,完全不需要手动维护这个属性:
- 内存计算属性:如果不需要存到数据库,直接在实体类中定义:
using System.Linq; // NotMapped表示这个属性不会被EF映射到数据库列 [NotMapped] public string LastChildrenName => Children?.LastOrDefault()?.Name; - 数据库计算列:如果需要把这个值存在数据库里,可以用数据库的计算列(以SQL Server为例):
- 实体类中标记:
[DatabaseGenerated(DatabaseGeneratedOption.Computed)] public string LastChildrenName { get; set; } - 在迁移文件中设置计算逻辑:
migrationBuilder.AddColumn<string>( name: "LastChildrenName", table: "Parents", type: "nvarchar(max)", nullable: true, computedColumnSql: "(SELECT TOP 1 [c].[Name] FROM [Children] AS [c] WHERE [c].[ParentId] = [p].[Id] ORDER BY [c].[Id] DESC)");
Children集合变化时,数据库会自动更新LastChildrenName,再也不用手动处理了。 - 实体类中标记:
内容的提问来源于stack exchange,提问作者Wiktor
相关产品推荐
相关产品推荐

