如何修复/避免ASP.NET Core + EF应用中SQL Azure系统版本化表的实体保存异常
如何修复/避免ASP.NET Core + EF应用中SQL Azure系统版本化表的实体保存异常
嘿,我来帮你梳理下怎么解决这个SQL Azure系统版本化表在EF+ASP.NET Core里的保存异常问题,结合你提到的Entities和关联Tasks表的场景,给你几个实用的方案:
1. 别让EF插手系统自动维护的版本字段
SQL Azure的系统版本化表会自动生成SysStartTime、SysEndTime这类字段来维护历史记录,EF如果瞎掺和这些字段的写入,肯定会报错。你得在实体配置里明确告诉EF:这些字段交给数据库自己管!
用数据注解的方式:
在实体类的对应属性上加上注解:
public class Entity { public int Id { get; set; } public string Name { get; set; } [DatabaseGenerated(DatabaseGeneratedOption.Computed)] public DateTime SysStartTime { get; set; } [DatabaseGenerated(DatabaseGeneratedOption.Computed)] public DateTime SysEndTime { get; set; } public ICollection<Task> Tasks { get; set; } } // Tasks表同理 public class Task { public int Id { get; set; } public string Description { get; set; } public int EntityId { get; set; } public Entity Entity { get; set; } [DatabaseGenerated(DatabaseGeneratedOption.Computed)] public DateTime SysStartTime { get; set; } [DatabaseGenerated(DatabaseGeneratedOption.Computed)] public DateTime SysEndTime { get; set; } }
用Fluent API的方式(更推荐,集中管理配置):
在DbContext的OnModelCreating方法里配置:
protected override void OnModelCreating(ModelBuilder modelBuilder) { // 配置Entities表的系统字段 modelBuilder.Entity<Entity>() .Property(e => e.SysStartTime) .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed); modelBuilder.Entity<Entity>() .Property(e => e.SysEndTime) .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed); // 配置Tasks表的系统字段 modelBuilder.Entity<Task>() .Property(t => t.SysStartTime) .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed); modelBuilder.Entity<Task>() .Property(t => t.SysEndTime) .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed); // 别忘了外键关联的配置 modelBuilder.Entity<Task>() .HasOne(t => t.Entity) .WithMany(e => e.Tasks) .HasForeignKey(t => t.EntityId); }
2. 事务操作要避开这些坑
你提到步骤里会在事务内执行保存操作,结合关联表的场景,要注意这几点:
(1)事务内用最新的实体,别用事务外加载的旧数据
如果在事务外先加载了实体,过了一会儿再在事务内修改,很可能因为数据被其他操作更新,或者EF的追踪状态失效导致异常。最好在事务内重新获取最新的实体:
using var transaction = await _context.Database.BeginTransactionAsync(); try { // 事务内重新拉取最新的实体和关联任务 var freshEntity = await _context.Entities.FindAsync(entityId); var freshTask = await _context.Tasks.FirstOrDefaultAsync(t => t.EntityId == freshEntity.Id); // 执行你的业务修改 freshEntity.Name = "更新后的名称"; freshTask.Description = "更新后的任务描述"; // 一次提交所有变更 await _context.SaveChangesAsync(); await transaction.CommitAsync(); } catch (Exception ex) { await transaction.RollbackAsync(); // 根据实际情况处理异常,比如给用户返回提示 throw new ApplicationException("保存失败,已回滚操作", ex); }
(2)处理并发冲突
系统版本化表的更新会生成历史记录,并发更新时很容易出现冲突。记得捕获DbUpdateConcurrencyException,然后重新加载数据再尝试:
catch (DbUpdateConcurrencyException concurrencyEx) { await transaction.RollbackAsync(); // 重新加载实体的最新状态 foreach (var entry in concurrencyEx.Entries) { await entry.ReloadAsync(); } // 可以选择重新执行修改逻辑,或者告知用户重试 throw new ApplicationException("数据已被其他用户修改,请刷新后重试", concurrencyEx); }
3. 别碰系统自动生成的历史表
SQL Azure会为每个系统版本化表生成对应的历史表(比如dbo.EntitiesHistory),这些表是数据库自动维护的,绝对不要用EF去查询、修改历史表,否则会破坏版本追踪逻辑,引发各种异常。
4. 升级到适配的EF Core版本
如果你的EF Core版本比较老,可能对SQL Azure系统版本化表的支持有bug(比如计算字段的追踪问题、事务内的保存逻辑问题)。建议升级到最新的稳定版EF Core(比如8.x系列),新版本对Azure SQL的特性支持更完善,能避开很多旧版本的坑。
内容来源于stack exchange
相关产品推荐
相关产品推荐

