Blazor Server与REST API共用后端时的EF Core生命周期处理问题
问题解决方案
问题根源
Blazor Server组件默认是Scoped生命周期,若你的IMyEntityService注册为Scoped(默认行为),则在组件未刷新/跳转的情况下,服务实例会被复用,其内部注入的IMyContext(即使配置为Transient)也会跟着服务实例被持续持有。第一次保存后,EF Core上下文已跟踪了对应ID的实体,第二次保存时转换DTO生成的新实体ID与已跟踪实体重复,就会触发跟踪冲突错误。
解决方案(兼顾Blazor Server与REST API复用)
方案1:将IMyEntityService注册为Transient
修改服务注册逻辑,让每次调用Save方法时都获取全新的服务实例和上下文实例,从根源避免跟踪冲突。
在Program.cs或服务配置类中:
// 替换原Scoped注册为Transient services.AddTransient<IMyEntityService, MyEntityService>();
该方案完全兼容REST API场景(REST每次请求都会生成新的服务实例),无需修改业务代码,实现成本最低。
方案2:在Save方法中处理实体跟踪状态
若需要保持服务的Scoped生命周期,可在Save方法中手动处理EF Core的实体跟踪逻辑,避免重复跟踪冲突:
public async Task<int> Save(DtoMyEntity dtoToSave) { _logger.LogTrace("{method}({dtoToSave})", nameof(Save), dtoToSave); // 将DTO转换为实体 var entity = ConvertDtoToEntity(dtoToSave); // 检查上下文是否已跟踪该实体 var trackedEntity = _context.MyEntities.Local.FirstOrDefault(e => e.Id == entity.Id); if (trackedEntity != null) { // 若已跟踪,直接更新现有实体的属性 _context.Entry(trackedEntity).CurrentValues.SetValues(entity); } else { // 未跟踪则根据ID判断是新增还是更新 if (entity.Id == 0) { _context.MyEntities.Add(entity); } else { _context.MyEntities.Attach(entity); _context.Entry(entity).State = EntityState.Modified; } } await _context.SaveChangesAsync().ConfigureAwait(false); return entity.Id; }
或使用EF Core的Update方法简化逻辑(自动处理跟踪状态):
public async Task<int> Save(DtoMyEntity dtoToSave) { _logger.LogTrace("{method}({dtoToSave})", nameof(Save), dtoToSave); var entity = ConvertDtoToEntity(dtoToSave); // EF Core自动处理:若实体已被跟踪则更新属性,否则附加为修改状态 _context.MyEntities.Update(entity); await _context.SaveChangesAsync().ConfigureAwait(false); return entity.Id; }
此方案无需修改依赖注入配置,同时适配Blazor Server和REST API场景。
方案3:查询时使用无跟踪模式
若Save方法中存在先查询实体再更新的逻辑,确保查询时使用AsNoTracking,避免查询结果被上下文跟踪:
// 查询时禁用跟踪 var entity = await _context.MyEntities.AsNoTracking() .FirstOrDefaultAsync(e => e.Id == dtoToSave.Id); // 后续修改实体并保存
内容的提问来源于stack exchange,提问作者Patrick
相关产品推荐
相关产品推荐

