一对多关系中软删除的实现难题及优化需求
优化关联实体软删除的几种可行方案
方案1:EF Core SaveChanges拦截器(通用简洁,适配多层级关联)
通过自定义拦截器,在保存变更时自动递归标记所有关联的BaseModel派生实体为软删除状态,无需手动处理每个实体的导航属性。
实现拦截器
public class SoftDeleteInterceptor : SaveChangesInterceptor { public override ValueTask<InterceptionResult<int>> SavingChangesAsync( DbContextEventData eventData, InterceptionResult<int> result, CancellationToken cancellationToken = default) { if (eventData.Context is null) return base.SavingChangesAsync(eventData, result, cancellationToken); // 捕获所有被标记为删除的BaseModel实体 var deletedEntities = eventData.Context.ChangeTracker.Entries<BaseModel>() .Where(e => e.State == EntityState.Deleted); foreach (var entry in deletedEntities) { // 替换硬删除为软删除标记 entry.State = EntityState.Modified; entry.Entity.IsDeleted = true; // 递归处理关联实体 MarkRelatedEntitiesAsDeleted(entry); } return base.SavingChangesAsync(eventData, result, cancellationToken); } private void MarkRelatedEntitiesAsDeleted(EntityEntry entry) { foreach (var navigation in entry.Navigations) { // 处理集合类型导航属性 if (navigation.IsCollection) { if (navigation.CurrentValue is IEnumerable<BaseModel> relatedEntities) { foreach (var relatedEntity in relatedEntities) { var relatedEntry = entry.Context.Entry(relatedEntity); if (relatedEntry.State != EntityState.Deleted && relatedEntry.State != EntityState.Modified) { relatedEntry.State = EntityState.Modified; relatedEntity.IsDeleted = true; MarkRelatedEntitiesAsDeleted(relatedEntry); } } } } // 处理单个实体类型导航属性 else { if (navigation.CurrentValue is BaseModel relatedEntity) { var relatedEntry = entry.Context.Entry(relatedEntity); if (relatedEntry.State != EntityState.Deleted && relatedEntry.State != EntityState.Modified) { relatedEntry.State = EntityState.Modified; relatedEntity.IsDeleted = true; MarkRelatedEntitiesAsDeleted(relatedEntry); } } } } } }
注册拦截器到DbContext
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.AddInterceptors(new SoftDeleteInterceptor()); }
简化后的业务代码
public async Task<string> Handle(DeleteCourseRequest request, CancellationToken cancellationToken) { var course = await _baseService.GetByPropertyAsyncWithoutMap(c => c.Id == request.Id); // 直接调用删除,拦截器自动处理所有关联实体 await _baseService.Delete(course); return "Done"; }
提示:无需手动
Include导航属性,EF Core追踪或延迟加载会自动识别关联实体。
方案2:批量更新(性能最优,避免加载实体)
如果追求极致性能,直接生成SQL批量更新语句,跳过实体加载步骤,适合固定关联结构的场景。
public async Task<string> Handle(DeleteCourseRequest request, CancellationToken cancellationToken) { // 更新主实体 await _dbContext.Courses .Where(c => c.Id == request.Id) .ExecuteUpdateAsync(s => s.SetProperty(c => c.IsDeleted, true), cancellationToken); // 更新关联的Centers await _dbContext.Centers .Where(c => c.CourseId == request.Id) .ExecuteUpdateAsync(s => s.SetProperty(c => c.IsDeleted, true), cancellationToken); // 更新关联的Sections await _dbContext.Sections .Where(s => s.CourseId == request.Id) .ExecuteUpdateAsync(s => s.SetProperty(c => c.IsDeleted, true), cancellationToken); // 多层级关联示例:更新Sections下的子实体 await _dbContext.SubSections .Where(ss => ss.SectionId.In(_dbContext.Sections.Where(s => s.CourseId == request.Id).Select(s => s.Id))) .ExecuteUpdateAsync(s => s.SetProperty(c => c.IsDeleted, true), cancellationToken); return "Done"; }
优势:无内存实体加载开销,执行效率最高;缺点:需手动编写每个关联实体的更新逻辑。
方案3:BaseService封装通用递归方法
若不想用拦截器,可在基础服务中封装递归标记方法,需提前Include所有需要处理的导航属性。
封装通用方法
public class BaseService<T> where T : BaseModel { // ... 现有方法 ... public void MarkEntityAndRelatedAsDeleted(T entity, DbContext context) { entity.IsDeleted = true; var entry = context.Entry(entity); foreach (var navigation in entry.Navigations) { if (navigation.IsCollection) { if (navigation.CurrentValue is IEnumerable<BaseModel> relatedEntities) { foreach (var related in relatedEntities) { if (!related.IsDeleted) MarkEntityAndRelatedAsDeleted(related, context); } } } else { if (navigation.CurrentValue is BaseModel relatedEntity && !relatedEntity.IsDeleted) MarkEntityAndRelatedAsDeleted(relatedEntity, context); } } } }
业务代码调用
public async Task<string> Handle(DeleteCourseRequest request, CancellationToken cancellationToken) { var course = await _baseService.GetByPropertyAsyncWithoutMap(c => c.Id == request.Id, c => c.Include(c => c.Centers) .Include(c => c.Sections) .ThenInclude(s => s.SubSections)); // 按需Include多层级导航 _baseService.MarkEntityAndRelatedAsDeleted(course, _dbContext); await _dbContext.SaveChangesAsync(cancellationToken); return "Done"; }
关键配套配置
确保全局查询过滤器自动过滤软删除数据,避免业务代码重复判断:
protected override void OnModelCreating(ModelBuilder modelBuilder) { foreach (var entityType in modelBuilder.Model.GetEntityTypes()) { if (typeof(BaseModel).IsAssignableFrom(entityType.ClrType)) { modelBuilder.Entity(entityType.ClrType) .HasQueryFilter(e => EF.Property<bool>(e, "IsDeleted") == false); } } }
内容的提问来源于stack exchange,提问作者Mohamed Alaa
相关产品推荐
相关产品推荐

