EF Core中IExecutionStrategy事务重试是否需每次新建DbContext?
在IExecutionStrategy事务重试中必须新建DbContext实例吗?
是的,必须每次通过IDbContextFactory新建DbContext实例,否则会因DbContext的状态污染引发各类bug,比如你遇到的重复添加实体、变更追踪冲突问题。
问题根源:DbContext的有状态特性
DbContext自带变更追踪器,会记录所有被添加、修改、删除的实体。当IExecutionStrategy触发重试时,如果复用同一个DbContext实例,之前操作留下的实体记录依然存在于追踪器中。重试执行代码逻辑时,会再次执行添加实体的操作,导致同一个实体被重复追踪,最终SaveChanges时要么插入重复数据,要么触发主键/唯一约束异常。
结合你的示例分析
以你给出的第二个代码示例为例:
strategy.ExecuteAsync( async () => { await using var transaction = context.Database.BeginTransactionAsync; var user = new User( id: 0, name: "biba"); context.Add(user); // 此处抛出TimeoutException触发重试 var products = await context.Products.ToListAsync(); await context.SaveChangesAsync(); });
第一次执行时,context.Add(user)已将User实例加入变更追踪器;触发重试后,复用同一个context再次执行context.Add(user),此时追踪器里已有该User的记录,SaveChanges会尝试插入第二条相同的User数据,完全不符合业务预期。
你的幂等令牌场景同理:重试时重复添加同一个幂等令牌实例,直接引发DbUpdateException。
正确的实现方式
每次重试逻辑内部都通过IDbContextFactory创建全新的DbContext实例,确保每次重试的上下文都是干净无状态的:
var strategy = _dbContextFactory.CreateDbContext().Database.CreateExecutionStrategy(); await strategy.ExecuteAsync(async () => { // 每次重试都新建DbContext await using var context = _dbContextFactory.CreateDbContext(); await using var transaction = await context.Database.BeginTransactionAsync(); var user = new User(id: 0, name: "biba"); context.Add(user); var products = await context.Products.ToListAsync(); await context.SaveChangesAsync(); await transaction.CommitAsync(); });
EF Core的执行策略设计初衷是保证重试操作的无状态性,复用有状态的DbContext会打破这个设计原则,引发不可预测的问题。虽然部分场景下可能暂时未出现bug,但这属于不安全写法,官方推荐始终在重试逻辑内使用新的DbContext实例。
内容的提问来源于stack exchange,提问作者Not_Ilya
相关产品推荐
相关产品推荐

