You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 08:01:30