如何让DataContext同时集成Duende Identity Server与数据库审计功能?
解决方案
思路说明
C#不支持类的多重继承,因此无法让DataContext同时继承ApiAuthorizationDbContext<ApplicationUser>和AuditableIdentityContext。我们可以通过迁移审计逻辑、EF Core拦截器或调整继承链三种方式,将审计功能整合到基于ApiAuthorizationDbContext的上下文之中,同时保留Duende Identity Server的相关功能。
方案1:直接迁移审计逻辑到DataContext
如果你的AuditableIdentityContext核心逻辑是在SaveChanges/SaveChangesAsync中处理审计字段(如创建人、创建时间、更新人、更新时间),可以直接将这些逻辑搬到继承ApiAuthorizationDbContext的DataContext里:
- 定义审计字段接口(若之前未定义):
public interface IAuditableEntity { string CreatedBy { get; set; } DateTime CreatedDate { get; set; } string UpdatedBy { get; set; } DateTime UpdatedDate { get; set; } }
- 让业务实体实现该接口:
public class Product : IAuditableEntity { public int Id { get; set; } public string Name { get; set; } // 审计字段 public string CreatedBy { get; set; } public DateTime CreatedDate { get; set; } public string UpdatedBy { get; set; } public DateTime UpdatedDate { get; set; } }
- 修改
DataContext,重写保存方法并注入审计逻辑:
public class DataContext : ApiAuthorizationDbContext<ApplicationUser> { private readonly IHttpContextAccessor _httpContextAccessor; public DataContext(DbContextOptions<DataContext> options, IHttpContextAccessor httpContextAccessor) : base(options) { _httpContextAccessor = httpContextAccessor; } // 自定义DbSet public DbSet<Product> Products { get; set; } public override int SaveChanges(bool acceptAllChangesOnSuccess) { ApplyAuditRules(); return base.SaveChanges(acceptAllChangesOnSuccess); } public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default) { ApplyAuditRules(); return await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken); } private void ApplyAuditRules() { var currentUser = _httpContextAccessor.HttpContext?.User?.Identity?.Name ?? "System"; var now = DateTime.UtcNow; foreach (var entry in ChangeTracker.Entries<IAuditableEntity>()) { switch (entry.State) { case EntityState.Added: entry.Entity.CreatedBy = currentUser; entry.Entity.CreatedDate = now; entry.Entity.UpdatedBy = currentUser; entry.Entity.UpdatedDate = now; break; case EntityState.Modified: entry.Entity.UpdatedBy = currentUser; entry.Entity.UpdatedDate = now; // 锁定创建字段,防止被修改 entry.Property(x => x.CreatedBy).IsModified = false; entry.Property(x => x.CreatedDate).IsModified = false; break; } } // 若需记录审计日志到数据库,可在此生成AuditLog实体并添加到DbSet // var logs = GenerateAuditLogs(); // AuditLogs.AddRange(logs); } }
方案2:用EF Core拦截器实现解耦审计
如果审计逻辑原本是通过拦截器实现的,可直接将拦截器注册到DataContext中,无需修改上下文继承关系:
- 创建审计拦截器:
public class AuditInterceptor : SaveChangesInterceptor { private readonly IHttpContextAccessor _httpContextAccessor; public AuditInterceptor(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } public override InterceptionResult<int> SavingChanges(DbContextEventData eventData, InterceptionResult<int> result) { ApplyAuditRules(eventData.Context); return base.SavingChanges(eventData, result); } public override ValueTask<InterceptionResult<int>> SavingChangesAsync(DbContextEventData eventData, InterceptionResult<int> result, CancellationToken cancellationToken = default) { ApplyAuditRules(eventData.Context); return base.SavingChangesAsync(eventData, result, cancellationToken); } private void ApplyAuditRules(DbContext context) { if (context == null) return; var currentUser = _httpContextAccessor.HttpContext?.User?.Identity?.Name ?? "System"; var now = DateTime.UtcNow; foreach (var entry in context.ChangeTracker.Entries<IAuditableEntity>()) { switch (entry.State) { case EntityState.Added: entry.Entity.CreatedBy = currentUser; entry.Entity.CreatedDate = now; entry.Entity.UpdatedBy = currentUser; entry.Entity.UpdatedDate = now; break; case EntityState.Modified: entry.Entity.UpdatedBy = currentUser; entry.Entity.UpdatedDate = now; entry.Property(x => x.CreatedBy).IsModified = false; entry.Property(x => x.CreatedDate).IsModified = false; break; } } } }
- 在服务注册时添加拦截器:
// 注册HttpContextAccessor用于获取当前用户 builder.Services.AddHttpContextAccessor(); builder.Services.AddScoped<AuditInterceptor>(); // 配置DataContext并注入拦截器 builder.Services.AddDbContext<DataContext>((sp, options) => { var interceptor = sp.GetRequiredService<AuditInterceptor>(); options.UseSqlServer("Your_Connection_String") .AddInterceptors(interceptor); }); // 保留Duende Identity Server配置 builder.Services.AddIdentityServer() .AddApiAuthorization<ApplicationUser, DataContext>();
方案3:调整AuditableIdentityContext的继承链(自定义场景)
如果AuditableIdentityContext是你自行实现的,可以修改它的继承关系,让它继承ApiAuthorizationDbContext<ApplicationUser>,再让DataContext继承这个修改后的审计上下文:
// 修改AuditableIdentityContext的基类 public class AuditableIdentityContext : ApiAuthorizationDbContext<ApplicationUser> { private readonly IHttpContextAccessor _httpContextAccessor; public AuditableIdentityContext(DbContextOptions<AuditableIdentityContext> options, IHttpContextAccessor httpContextAccessor) : base(options) { _httpContextAccessor = httpContextAccessor; } // 原审计逻辑,重写保存方法 public override int SaveChanges(bool acceptAllChangesOnSuccess) { ApplyAuditRules(); return base.SaveChanges(acceptAllChangesOnSuccess); } private void ApplyAuditRules() { // 审计逻辑实现... } } // DataContext继承修改后的审计上下文 public class DataContext : AuditableIdentityContext { public DataContext(DbContextOptions<DataContext> options, IHttpContextAccessor httpContextAccessor) : base(options, httpContextAccessor) { } // 自定义DbSet... }
注意事项
- 确保注册
IHttpContextAccessor服务,否则无法获取当前登录用户信息。 - 若审计逻辑包含审计日志表,需在
DataContext中添加对应DbSet,并通过EF迁移更新数据库结构。 - 整合后需测试Identity Server核心功能(令牌生成、用户管理)与审计功能(字段自动填充、日志记录)是否正常运行。
内容的提问来源于stack exchange,提问作者Tim Cadieux
相关产品推荐
相关产品推荐

