EF Command Interceptor拦截事务内命令时如何存数据或忽略该类命令
问题根因
你遇到的超时是事务死锁导致的:TransactionScope默认会将当前作用域内所有数据库操作纳入同一个分布式事务,你拦截器中新建的ApplicationDbContext会自动加入外层未提交的事务,外层事务等待拦截器逻辑执行完成才能提交,拦截器的SaveChanges又等待外层事务释放资源,形成死锁最终超时。
方案1:拦截器内正常写入日志(推荐,符合日志需求)
只需要让拦截器的日志写入逻辑不加入外层事务即可,用TransactionScopeOption.Suppress声明独立事务范围,日志写入不受外层事务影响,就算外层业务回滚也能保留操作日志,符合日志的审计需求。
修改后的拦截器代码:
class EFCommandInterceptor : IDbCommandInterceptor { public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext) { var log = new LogModel { DateTime = DateTime.Now, Log = "Log" }; // 声明Suppress范围,内部操作不加入当前环境事务 using (var txScope = new TransactionScope(TransactionScopeOption.Suppress)) { using (var context = new ApplicationDbContext()) { context.Logs.Add(log); // 建议直接用同步SaveChanges,避免.Result阻塞带来的额外问题 var res = context.SaveChanges(); } txScope.Complete(); } } }
方案2:忽略事务内的命令
如果不需要记录事务内的操作日志,只需要在拦截器逻辑开头判断是否存在当前环境事务,存在则直接跳过即可:
public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext) { // 存在环境事务则直接跳过当前逻辑 if (Transaction.Current != null) return; // 原有日志写入逻辑 var log = new LogModel { DateTime = DateTime.Now, Log = "Log" }; using (var context = new ApplicationDbContext()) { context.Logs.Add(log); context.SaveChanges(); } }
注意事项
- 方案1中日志写入是独立提交的,不会随外层业务事务回滚,符合绝大多数日志场景的需求
- 不要在同步拦截器方法中使用
SaveChangesAsync().Result,同步调用直接用SaveChanges即可,避免异步同步混用导致的线程阻塞问题
内容的提问来源于stack exchange,提问作者Tomas
相关产品推荐
相关产品推荐

