EF Core 本身不内置审计日志功能,但可以通过拦截 SaveChanges 过程,在数据写入数据库前自动捕获实体的变更状态,从而实现轻量、统一的审计日志记录。核心思路是:在
DbContext中重写
SaveChanges/
SaveChangesAsync,遍历所有被跟踪的实体,提取新增、修改、删除操作及字段级变化,并写入自定义审计日志表。
1. 定义审计日志实体
先创建一个标准的日志实体,用于存储操作人、时间、实体类型、操作类型(Add/Update/Delete)、关键字段变更等信息:
AuditLogId:主键(Guid 或 long) UserId:当前操作用户 ID(需从上下文或 ClaimsPrincipal 获取) EntityType:被操作的实体类名(如 "Order") EntityId:被操作实体的主键值(支持 string/int/Guid) Action:操作类型("Created", "Updated", "Deleted") ChangedFields:JSON 字符串,记录字段名、旧值、新值(仅 Update 时有差异) Timestamp:UTC 时间戳2. 在 DbContext 中注入审计逻辑
重写
SaveChanges方法,利用 EF Core 的
ChangeTracker获取变更状态: 调用
ChangeTracker.Entries()遍历所有状态为
Added、
Modified、
Deleted的实体 对每个条目,提取主键值(
entry.Metadata.FindPrimaryKey().Properties)和变更详情 对于
Modified条目,用
entry.OriginalValues和
entry.CurrentValues对比字段差异 构造
AuditLog实体并添加到当前上下文(注意:避免递归触发审计) 建议将审计日志实体加入
DbContext时使用
Entry(log).State = EntityState.Added而非
Add(),防止与主 SaveChanges 冲突
3. 处理用户上下文与线程安全
获取当前用户 ID 是常见难点,推荐以下方式:
通过构造函数注入IHttpContextAccessor(需在
Program.cs注册),从中读取
User.Identity.Name或自定义 Claim 若非 Web 环境(如后台服务),可依赖外部传入的
CurrentUserId(例如通过作用域服务或显式参数) 避免在审计逻辑中直接 await 异步操作(如查用户信息),保持同步处理;如必须异步,请改用
SaveChangesAsync并确保日志写入也异步完成
4. 可选增强:字段级过滤与忽略
不是所有字段都需要审计(如
UpdatedAt、
RowVersion)。可通过以下方式控制: 给实体属性加自定义特性(如
[AuditIgnore]),在审计逻辑中跳过标记字段 约定忽略以
Is、
Has、
Last开头的布尔/时间戳字段 为敏感字段(如密码)强制脱敏:写入日志前替换为
"[REDACTED]"
基本上就这些。不需要第三方库也能跑起来,关键是把变更提取逻辑写稳、字段对比做准、用户上下文拿得对。小项目够用,大系统可在此基础上接入消息队列或 Elasticsearch 做异步归档。
