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

使用SaveChangesInterceptor实现重试模式处理DbUpdateConcurrencyException

能否通过SaveChangesInterceptor实现重试模式?

可以通过SaveChangesInterceptor实现重试模式,但你当前的实现存在逻辑缺陷,无法正确触发重试流程,下面逐一分析问题并给出修正方案。

现有代码的问题

1. SavingChangesAsync方法的核心问题

你当前的代码直接返回result,并未触发实际的数据库保存操作,因此try块内永远不会捕获到异常,循环重试的逻辑完全无效。该方法的作用是拦截保存操作的前置流程,必须调用基类方法或触发实际保存才能执行数据库操作并捕获异常。

2. SaveChangesFailedAsync方法的问题

  • 直接调用eventData.Context.SaveChangesAsync会触发拦截器的递归调用,可能陷入无限循环;
  • InterceptionResult.Suppress()无法直接生效,需要通过修改事件数据或返回值来抑制后续异常处理逻辑。

修正后的实现方案(EF Core 5.0)

以下是针对指定实体类型实现并发异常重试的拦截器代码:

public class RetrySaveChangesInterceptor<TEntity> : SaveChangesInterceptor
    where TEntity : class
{
    private readonly int _maxRetries;
    private readonly int _delay;

    public RetrySaveChangesInterceptor(int maxRetries, int delay)
    {
        _maxRetries = maxRetries;
        _delay = delay;
    }

    public override async ValueTask<InterceptionResult<int>> SavingChangesAsync(DbContextEventData eventData, InterceptionResult<int> result, CancellationToken cancellationToken = default)
    {
        int tryCount = 0;
        while (tryCount < _maxRetries)
        {
            try
            {
                // 调用基类方法触发实际的数据库保存操作
                var saveResult = await base.SavingChangesAsync(eventData, result, cancellationToken);
                return saveResult;
            }
            catch (DbUpdateConcurrencyException ex)
            {
                // 仅处理指定类型实体的并发异常
                if (ex.Entries.All(e => e.Entity.GetType() != typeof(TEntity)))
                {
                    throw;
                }

                Console.WriteLine($"第{tryCount + 1}次重试");

                // 重新加载实体最新数据,解决并发冲突
                foreach (var entry in ex.Entries)
                {
                    if (entry.Entity is TEntity)
                    {
                        await entry.ReloadAsync(cancellationToken);
                    }
                }

                // 延迟后重试
                await Task.Delay(_delay, cancellationToken);
            }
            catch (DbUpdateException ex)
            {
                Console.WriteLine($"更新异常:{ex.Message}");
                // 非并发类更新异常可根据实际情况决定是否重试,此处直接抛出
                throw;
            }

            tryCount++;
        }

        throw new DbUpdateConcurrencyException($"已超出最大重试次数:{_maxRetries}");
    }
}

额外说明

  • 拦截器方式相比重写DbContext的SaveChanges方法,优势在于逻辑可复用,无需修改每个DbContext的实现;
  • 可优化重试策略,比如采用指数退避延迟(每次重试延迟翻倍),减少数据库压力;
  • 需明确区分可重试异常类型,仅对临时异常(如并发冲突、短暂网络波动)执行重试,避免无效重试。

内容的提问来源于stack exchange,提问作者M.Aslzad

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 13:07:41