EF Core如何在SaveChanges时检测父实体关联的子实体变更?
处理关联实体变更时的客户端通知问题(ASP.NET Core Web API)
初始实现思路
开发ASP.NET Core Web API时,需要在用户查看的实体发生变更时通知客户端更新数据。最初的实现是重写DbContext的SaveChanges方法,遍历所有Parent实体,检查其状态是否为未变更,以此检测父实体的变更:
public override int SaveChanges() { foreach(var p in Parents) { if (Entry(p).State != EntityState.Unchanged) { // 通知客户端 } } return base.SaveChanges(); }
遇到的问题
上述方案只能检测Parent实体自身的变更,当子对象发生变更时无法被检测到——即使查看Entry的Properties、Navigations等属性,子实体的IsModified仍显示为false,变更追踪器也无法提供有效信息。
需求补充
需要覆盖所有与Parent相关的实体变更场景:不仅是子实体,还包括子实体的子实体(嵌套子实体),只要任何关联Parent的内容发生变更,都需要通知客户端更新视图。
最终解决方案(兼容删除场景)
后续开发中发现,之前的思路无法处理子实体删除的情况:当实体状态被设为Deleted或通过_context.DbSet<>.Remove()标记删除时,调用EntityEntry.Load()无法加载其关联关系,这是EF Core 6到7的预期破坏性变更。
最终采用的方案是从所有变更实体出发,递归查找最近的Parent,而非遍历所有Parent及其子实体。针对删除场景,临时将条目状态设为Modified,找到受影响的父实体后再改回Deleted。完整代码如下:
public DbSet<Parent> Parents { get; set; } public override int SaveChanges() { DetectAndNotifyChanges(); return base.SaveChanges(); } private void DetectAndNotifyChanges() { List<EntityEntry> changedEntries = new List<EntityEntry>(); foreach (var entity in ChangeTracker.Entries().Where(x => x.State != EntityState.Unchanged)) { changedEntries.Add(entity); } foreach(var entry in changedEntries) { bool deleted = false; if(entry.State == EntityState.Deleted) { deleted = true; entry.State = EntityState.Modified; } int changedParentId = RecursiveSearch(entry); if(changedParentId != -1) { // 通知客户端 } if(deleted) { entry.State = EntityState.Deleted; } } } private List<dynamic> checkedEntries = new List<dynamic>(); private int RecursiveSearch(dynamic entry) { if (entry.Entity.GetType().BaseType == Parents.GetType().GetGenericArguments()[0]) { return entry.Entity.Id; } foreach (var navigationEntry in entry.Navigations) { if(navigationEntry is CollectionEntry collectionEntry) { collectionEntry.Load(); foreach(var referencedEntity in collectionEntry.CurrentValue) { var referencedEntry = Entry(referencedEntity); if (!checkedEntries.Contains(referencedEntry)) { checkedEntries.Add(referencedEntry); return RecursiveSearch(referencedEntry); } } } else if (navigationEntry is ReferenceEntry referenceEntry) { referenceEntry.Load(); dynamic referencedEntry = referenceEntry.TargetEntry; if(referencedEntry != null) { if (!checkedEntries.Any(x => x.Entity.GetType() == referencedEntry.Entity.GetType() && x.Entity.Id == referencedEntry.Entity.Id)) { checkedEntries.Add(referencedEntry); return RecursiveSearch(referencedEntry); } } } } return -1; }
内容的提问来源于stack exchange,提问作者Lupe
相关产品推荐
相关产品推荐

