Entity Framework + SQL Server时态表:更新后删除丢失最后更新的问题
问题描述
我正在使用.NET Framework 4.8开发ASP.NET Web API应用程序,数据库采用SQL Server 2017的**时态表(Temporal tables)**特性追踪存储行的变更,数据访问使用Entity Framework作为ORM。整体运行正常,但执行以下代码先更新记录后立即删除时:
var recordForDelete = await _dbContext.record .FirstOrDefaultAsync(x => x.ID == id); recordForDelete.LastModifiedBy = (int)SystemActions.System; await _dbContext.SaveChangesAsync(); _dbContext.record.Remove(recordForDelete); await dbContext.SaveChangesAsync();
最后一次更新未被捕获到系统版本表中,记录直接被标记为已删除(通过设置系统结束时间),丢失了最后更新记录。这似乎是一种竞争条件,因为在两次调用_dbContext.SaveChangesAsync()之间添加Task.Delay可解决该问题。我也尝试改为_dbContext.SaveChanges(),但问题依旧。
解决建议
重新加载实体后执行删除
问题根源是EF上下文仍在跟踪更新前的实体状态,导致两次操作的SQL被优化,时态表无法生成中间更新的版本记录。解决方式是更新保存后,将原实体从上下文分离,重新从数据库加载最新状态再执行删除:// 执行更新并保存 var recordForUpdate = await _dbContext.record.FirstOrDefaultAsync(x => x.ID == id); recordForUpdate.LastModifiedBy = (int)SystemActions.System; await _dbContext.SaveChangesAsync(); // 分离原实体,避免上下文跟踪干扰 _dbContext.Entry(recordForUpdate).State = EntityState.Detached; // 重新加载最新状态的实体 var recordForDelete = await _dbContext.record.FirstOrDefaultAsync(x => x.ID == id); // 执行删除操作 _dbContext.record.Remove(recordForDelete); await _dbContext.SaveChangesAsync();手动插入历史版本记录
如果需要在同一上下文内完成操作,可在更新保存后,手动将更新前的实体状态插入到对应的时态历史表,确保更新记录被留存:var record = await _dbContext.record.FirstOrDefaultAsync(x => x.ID == id); // 克隆更新前的实体状态 var preUpdateRecord = new Record { ID = record.ID, LastModifiedBy = record.LastModifiedBy, SysStartTime = record.SysStartTime // 复制其他需要追踪的字段 }; record.LastModifiedBy = (int)SystemActions.System; await _dbContext.SaveChangesAsync(); // 手动插入到历史表(替换为你的实际历史表名称) _dbContext.Database.ExecuteSqlCommand( "INSERT INTO [recordHistory] (ID, LastModifiedBy, SysStartTime, SysEndTime) VALUES (@p0, @p1, @p2, @p3)", preUpdateRecord.ID, preUpdateRecord.LastModifiedBy, preUpdateRecord.SysStartTime, DateTime.UtcNow ); _dbContext.record.Remove(record); await _dbContext.SaveChangesAsync();确认EF时态表配置正确性
确保实体类中的系统时间列被标记为数据库自动生成,避免EF干预这些列的维护:[DatabaseGenerated(DatabaseGeneratedOption.Computed)] public DateTime SysStartTime { get; set; } [DatabaseGenerated(DatabaseGeneratedOption.Computed)] public DateTime SysEndTime { get; set; }调整事务隔离级别
尝试将两次操作放在独立的READ COMMITTED级别事务中,强制SQL Server为每次操作生成独立的版本记录:// 更新操作单独事务 using (var transaction = _dbContext.Database.BeginTransaction(System.Data.IsolationLevel.ReadCommitted)) { try { var record = await _dbContext.record.FirstOrDefaultAsync(x => x.ID == id); record.LastModifiedBy = (int)SystemActions.System; await _dbContext.SaveChangesAsync(); transaction.Commit(); } catch { transaction.Rollback(); throw; } } // 删除操作单独事务 using (var transaction = _dbContext.Database.BeginTransaction(System.Data.IsolationLevel.ReadCommitted)) { try { var record = await _dbContext.record.FirstOrDefaultAsync(x => x.ID == id); _dbContext.record.Remove(record); await _dbContext.SaveChangesAsync(); transaction.Commit(); } catch { transaction.Rollback(); throw; } }
内容的提问来源于stack exchange,提问作者anselfinger
相关产品推荐
相关产品推荐

