如何用C#实现数据库的审计日志?步骤是什么?

如何用C#实现数据库的审计日志?步骤是什么?
最新回答
回眸丶时光冷

2026-10-20 12:55:51

在C#中通过重写EF的SaveChanges方法实现数据库审计日志的核心步骤如下:

  1. 设计审计日志表结构

    创建用于存储审计信息的表,包含关键字段:

    Id:主键

    TableName:被修改的表名

    RecordId:被修改记录的主键值

    Action:操作类型(Insert、Update、Delete)

    ChangedBy:操作用户(用户名或ID)

    ChangedAt:操作时间(默认UTC时间)

    OldValues:修改前的数据(JSON格式)

    NewValues:修改后的数据(JSON格式)

    IpAddress:客户端IP(可选)

    示例SQL:CREATE TABLE AuditLog ( Id INT IDENTITY(1,1) PRIMARY KEY, TableName NVARCHAR(100), RecordId NVARCHAR(50), Action NVARCHAR(10), ChangedBy NVARCHAR(100), ChangedAt DATETIME2 DEFAULT GETUTCDATE(), OldValues NVARCHAR(MAX), NewValues NVARCHAR(MAX));

  2. 创建审计日志实体类

    定义与日志表对应的C#实体类:public class AuditLog { public int Id { get; set; } public string TableName { get; set; } public string RecordId { get; set; } public string Action { get; set; } // Insert, Update, Delete public string ChangedBy { get; set; } public DateTime ChangedAt { get; set; } public string OldValues { get; set; } public string NewValues { get; set; }}

  3. 在DbContext中重写SaveChanges方法

    利用EF的ChangeTracker捕获增删改操作,生成审计记录:public override int SaveChanges() { var auditEntries = OnBeforeSaving("system"); // 默认用户,可替换为实际用户 var result = base.SaveChanges(); OnAfterSaving(); return result;}private List<AuditEntry> OnBeforeSaving(string userId) { var auditEntries = new List<AuditEntry>(); foreach (var entry in ChangeTracker.Entries()) { if (entry.Entity is AuditLog || entry.State == EntityState.Detached || entry.State == EntityState.Unchanged) continue; var auditEntry = new AuditEntry(entry) { TableName = entry.Entity.GetType().Name, ChangedBy = userId }; foreach (var property in entry.Properties) { string propertyName = property.Metadata.Name; if (property.Metadata.IsPrimaryKey()) { auditEntry.RecordId = property.CurrentValue?.ToString(); continue; } switch (entry.State) { case EntityState.Added: auditEntry.NewValues[propertyName] = property.CurrentValue; break; case EntityState.Deleted: auditEntry.OldValues[propertyName] = property.OriginalValue; break; case EntityState.Modified: if (property.IsModified) { auditEntry.OldValues[propertyName] = property.OriginalValue; auditEntry.NewValues[propertyName] = property.CurrentValue; } break; } } auditEntries.Add(auditEntry); } foreach (var auditEntry in auditEntries) { AuditLogs.Add(auditEntry.ToAudit()); } return auditEntries;}private void OnAfterSaving() { // 可用于清理或异步写入}

  4. 创建临时AuditEntry类

    辅助收集变更数据并转换为AuditLog实体:public class AuditEntry { public AuditEntry(EntityEntry entry) { Entry = entry; OldValues = new Dictionary<string, object>(); NewValues = new Dictionary<string, object>(); } public EntityEntry Entry { get; } public string TableName { get; set; } public string RecordId { get; set; } public string ChangedBy { get; set; } public Dictionary<string, object> OldValues { get; } = new(); public Dictionary<string, object> NewValues { get; } = new(); public AuditLog ToAudit() { var jsonSerializerOptions = new JsonSerializerOptions { WriteIndented = true }; return new AuditLog { TableName = TableName, RecordId = RecordId, Action = Entry.State.ToString(), ChangedBy = ChangedBy, ChangedAt = DateTime.UtcNow, OldValues = OldValues.Count != 0 ? JsonSerializer.Serialize(OldValues, jsonSerializerOptions) : null, NewValues = NewValues.Count != 0 ? JsonSerializer.Serialize(NewValues, jsonSerializerOptions) : null }; }}

  5. 注册DbSet并更新数据库

    在DbContext中添加AuditLog的DbSet:public DbSet<AuditLog> AuditLogs { get; set; }

    使用EF Core命令生成迁移并更新数据库:dotnet ef migrations add AddAuditLogTabledotnet ef database update

扩展功能建议

  • 异步保存:重写SaveChangesAsync方法以支持异步操作。
  • 过滤实体:通过自定义特性或配置排除特定实体不记录审计。
  • 记录IP地址:在OnBeforeSaving中从HTTP上下文获取客户端IP并写入AuditLog。
  • 性能优化:对高频操作考虑批量写入或异步日志存储。

通过以上步骤,调用SaveChanges()时所有变更将自动记录到审计表中,实现完整的数据库操作追踪。