You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Entity Framework 6中变更追踪异常处理及连接超时重试咨询

EF6 实现含自追踪的变更日志+连接超时重试方案

这场景我之前在项目里也遇到过,既要用实体追踪所有表(包括追踪表自身)的变更,又要处理保存异常避免递归,还要针对连接超时做重试,核心是重写DbContext的SaveChanges方法来实现这些逻辑,下面给你详细拆解:

核心实现思路

  1. 重写SaveChanges:拦截保存操作,嵌入异常处理和重试逻辑
  2. 异常分类处理:区分连接超时异常和其他异常,仅对超时做最多3次重试
  3. 清理待追踪实体:非超时异常或重试耗尽时,清除待保存的追踪实体,防止递归生成新的追踪记录
  4. 变更日志生成:在每次保存前自动收集所有实体的变更,生成追踪记录

关键代码实现

假设你的追踪实体为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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 06:35:08