.NET Core 7.0 EF Core继承审计字段无法保存问题求助
问题排查与解决方案
核心原因分析
你的审计字段无法写入数据库,大概率是以下两个原因之一:
- EF Core迁移未正确包含审计字段;
- 未实现审计字段的自动填充逻辑,导致字段值未被正确设置(或仅使用了默认值)。
步骤1:确认迁移包含审计字段
首先检查你的迁移文件(Migrations文件夹下的.cs文件),查看Up方法中是否存在创建Created、CreatedBy、LastModified、LastModifiedBy列的代码。如果没有:
- 删除现有的迁移文件;
- 重新生成迁移:
Add-Migration AddAuditFields - 更新数据库:
Update-Database
步骤2:实现审计字段自动填充逻辑
手动给每个实体赋值审计字段容易遗漏,推荐通过重写DbContext的SaveChanges方法或使用EF Core拦截器实现自动填充:
方案1:重写DbContext的SaveChanges方法
修改你的DataContext类,添加以下代码:
public class DataContext : DbContext, IApplicationDbContext { public DbSet<Category> Category => Set<Category>(); // 保留你原有构造函数配置 public DataContext(DbContextOptions<DataContext> options) : base(options) { } public override int SaveChanges(bool acceptAllChangesOnSuccess) { UpdateAuditFields(); return base.SaveChanges(acceptAllChangesOnSuccess); } public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default) { UpdateAuditFields(); return await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken); } private void UpdateAuditFields() { // 替换为获取当前登录用户ID的逻辑(比如从HttpContext或中介者上下文获取) var currentUserId = 1; var auditableEntries = ChangeTracker.Entries<BaseAuditableEntity>(); foreach (var entry in auditableEntries) { if (entry.State == EntityState.Added) { entry.Entity.Created = DateTime.UtcNow; entry.Entity.CreatedBy = currentUserId; } else if (entry.State == EntityState.Modified) { entry.Entity.LastModified = DateTime.UtcNow; entry.Entity.LastModifiedBy = currentUserId; // 禁止修改创建时的审计字段 entry.Property(x => x.Created).IsModified = false; entry.Property(x => x.CreatedBy).IsModified = false; } } } }
方案2:使用EF Core SaveChangesInterceptor
- 创建审计拦截器类:
public class AuditInterceptor : SaveChangesInterceptor { private readonly IHttpContextAccessor _httpContextAccessor; public AuditInterceptor(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } public override InterceptionResult<int> SavingChanges(DbContextEventData eventData, InterceptionResult<int> result) { UpdateAuditFields(eventData.Context); return base.SavingChanges(eventData, result); } public override ValueTask<InterceptionResult<int>> SavingChangesAsync(DbContextEventData eventData, InterceptionResult<int> result, CancellationToken cancellationToken = default) { UpdateAuditFields(eventData.Context); return base.SavingChangesAsync(eventData, result, cancellationToken); } private void UpdateAuditFields(DbContext? context) { if (context == null) return; // 从HttpContext获取当前用户ID,替换为你的实际用户ID获取逻辑 var userIdClaim = _httpContextAccessor.HttpContext?.User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier); var currentUserId = userIdClaim != null ? int.Parse(userIdClaim.Value) : 1; var auditableEntries = context.ChangeTracker.Entries<BaseAuditableEntity>(); foreach (var entry in auditableEntries) { if (entry.State == EntityState.Added) { entry.Entity.Created = DateTime.UtcNow; entry.Entity.CreatedBy = currentUserId; } else if (entry.State == EntityState.Modified) { entry.Entity.LastModified = DateTime.UtcNow; entry.Entity.LastModifiedBy = currentUserId; entry.Property(x => x.Created).IsModified = false; entry.Property(x => x.CreatedBy).IsModified = false; } } } }
- 在
Program.cs中注册拦截器:
builder.Services.AddHttpContextAccessor(); builder.Services.AddDbContext<DataContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")) .AddInterceptors(new AuditInterceptor(builder.Services.BuildServiceProvider().GetRequiredService<IHttpContextAccessor>())));
步骤3:种子数据中手动设置审计字段
种子数据执行时没有HttpContext,需要手动赋值审计字段:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Category>().HasData( new Category { Id = 1, Name = "示例分类", Description = "示例描述", Created = DateTime.UtcNow, CreatedBy = 1 // 替换为种子数据对应的用户ID }); }
内容的提问来源于stack exchange,提问作者Rahul Parab
相关产品推荐
相关产品推荐

