Entity Framework 6中变更追踪异常处理及连接超时重试咨询
EF6 实现含自追踪的变更日志+连接超时重试方案
这场景我之前在项目里也遇到过,既要用实体追踪所有表(包括追踪表自身)的变更,又要处理保存异常避免递归,还要针对连接超时做重试,核心是重写DbContext的SaveChanges方法来实现这些逻辑,下面给你详细拆解:
核心实现思路
- 重写SaveChanges:拦截保存操作,嵌入异常处理和重试逻辑
- 异常分类处理:区分连接超时异常和其他异常,仅对超时做最多3次重试
- 清理待追踪实体:非超时异常或重试耗尽时,清除待保存的追踪实体,防止递归生成新的追踪记录
- 变更日志生成:在每次保存前自动收集所有实体的变更,生成追踪记录
关键代码实现
假设你的追踪实体为ChangeLog,包含表名、操作类型、变更时间等字段,以下是完整的重写逻辑:
using System.Data.Entity; using System.Data.SqlClient; using System.Threading; using System.Collections.Generic; using System.Linq; public class YourDbContext : DbContext { // 假设你有日志记录器,可根据实际替换 private readonly ILogger _logger; public YourDbContext(string connectionString, ILogger logger) : base(connectionString) { _logger = logger; } public override int SaveChanges() { const int maxRetryAttempts = 3; int currentRetry = 0; while (currentRetry < maxRetryAttempts) { try { // 生成本次变更的追踪日志(包含ChangeLog表自身的变更) var changeLogs = GenerateChangeLogs(); AddRange(changeLogs); // 执行原生保存操作 return base.SaveChanges(); } catch (SqlException sqlEx) { currentRetry++; // 判断是否为Sql Server连接超时相关异常(错误码根据数据库调整) // -2: 连接超时; 10054: 连接被远程主机重置; 233: 管道连接失败 bool isConnectionTimeout = new[] {-2, 10054, 233}.Contains(sqlEx.Number); if (!isConnectionTimeout || currentRetry >= maxRetryAttempts) { // 非超时异常或重试次数耗尽,清理待保存的追踪日志 ClearPendingChangeLogs(); _logger.LogError(sqlEx, "数据库变更保存失败,已清理待处理追踪记录"); throw; // 可根据需求选择是否抛出,或返回错误码 } // 重试前短暂等待,避免高频重试加剧数据库压力 Thread.Sleep(1000); _logger.LogWarning($"连接超时,正在进行第{currentRetry}次重试"); } catch (Exception ex) { // 其他类型异常,直接清理追踪日志并记录 ClearPendingChangeLogs(); _logger.LogError(ex, "数据库变更保存失败,已清理待处理追踪记录"); throw; } } return 0; } /// <summary> /// 生成所有实体的变更日志 /// </summary> private List<ChangeLog> GenerateChangeLogs() { var logs = new List<ChangeLog>(); // 遍历所有被EF追踪的、状态变更的实体 foreach (var entry in ChangeTracker.Entries() .Where(e => e.State is EntityState.Added or EntityState.Modified or EntityState.Deleted)) { // 构建追踪日志实体,可根据需求扩展字段(比如变更内容、操作人等) var log = new ChangeLog { TableName = entry.Entity.GetType().Name, OperationType = entry.State.ToString(), ChangeTime = DateTime.UtcNow, // 若需要记录变更字段,可通过entry.OriginalValues和entry.CurrentValues对比 }; logs.Add(log); } return logs; } /// <summary> /// 清理待保存的追踪日志,防止递归生成新记录 /// </summary> private void ClearPendingChangeLogs() { // 找到所有处于新增/修改状态的ChangeLog实体 var pendingLogs = ChangeTracker.Entries<ChangeLog>() .Where(e => e.State is EntityState.Added or EntityState.Modified); foreach (var entry in pendingLogs) { // 新增的日志直接从追踪器移除,修改的日志重置为未变更状态 entry.State = entry.State == EntityState.Added ? EntityState.Detached : EntityState.Unchanged; } } }
关键注意事项
- 异常错误码适配:不同数据库的连接超时错误码不同,比如Oracle的超时错误码可能不一样,需要根据你使用的数据库调整判断逻辑
- 重试等待时间:可根据实际场景调整等待时长,比如数据库压力大时可延长至2-3秒
- 变更日志去重:如果重试时重新生成日志,要确保不会重复记录同一变更(不过因为每次重试都会重新收集当前实体状态,正常情况下不会重复)
- 事务一致性:EF6的
SaveChanges默认是事务性的,若需要跨多个操作的事务,可手动开启DbContext.Database.BeginTransaction()
内容的提问来源于stack exchange,提问作者M Bakardzhiev
相关产品推荐
相关产品推荐

