为何调用Logger的SaveChangesAsync会更新其他实体?
EF Core日志服务SaveChangesAsync意外修改关联实体问题
我用Logger服务记录每个接口的操作到数据表,但遇到异常:调用Logger服务里的await context.SaveChangesAsync()时,会意外更新另一个关联实体(Performances表)的数据。
相关代码
控制器代码
var model = await teamsService.GetTeamData("team_url"); await eventsLogger.LogInfo("className", "actionName", $"Get team data by url {name}", user); return Ok(model); // 不想等待Logger响应,但不用async会报错(此处不展开) // _ = eventsLogger.LogInfo("className", "actionName", $"Get team data by url {name}", user);
TeamsService的GetTeamData方法
var team = await dataContext.Teams.FirstOrDefaultAsync(x => x.UrlPath == urlPath); team.Views += 1; dataContext.Update(team); await dataContext.SaveChangesAsync(); var performances = await dataContext.Performances .Where(x => x.TeamId == team.Id) .ToListAsync(); // 将team和performances作为嵌套结果返回;
LogsService的LogInfo方法
var @event = new Event() { /* ... 参数赋值 */ }; dataContext.Add(@event); await dataContext.SaveChangesAsync(); // 此处出现问题
Startup服务配置
services.AddDbContext<DataContext>(options => { options.UseSqlServer( Configuration.GetConnectionString("MainConnection"), sqlServerOptions => sqlServerOptions.CommandTimeout(120) ); }); services.AddScoped<IEventsLogger, EventsLogger>(); services.AddScoped<ITeamsService, TeamsService>();
排查情况
- 调换控制器代码顺序,先执行日志记录再获取团队数据,问题消失,但不想采用这个方案
- 移除
team.Views += 1代码后,仅在保存日志时会出现该问题 - 被修改的是Performances实体的
Year字符串字段和Count整数字段,数据是被重置而非更新为旧版本
问题原因
核心原因是同一个请求内,TeamsService和EventsLogger共享同一个Scoped生命周期的DbContext实例:
- TeamsService中先修改并提交了Team实体的更新
- 接着查询并加载了Performances实体到DbContext的跟踪缓存中
- 当调用EventsLogger的SaveChangesAsync时,DbContext会自动检测所有被跟踪的实体。如果此时Performances实体的某些字段在内存中的值和数据库中的值存在差异(可能是EF Core跟踪时的对比逻辑异常,或者实体属性被意外修改),就会被当作变更提交到数据库。
调换顺序后没问题,是因为先执行日志时,DbContext还未加载Performances实体,SaveChangesAsync只会处理Event实体,不会影响其他表。
解决方案
- 禁用Performances实体的跟踪:在查询时添加
AsNoTracking(),让DbContext不跟踪这些实体,后续SaveChangesAsync就不会检测它们的变化
var performances = await dataContext.Performances .Where(x => x.TeamId == team.Id) .AsNoTracking() .ToListAsync();
- 给Logger服务单独使用新的DbContext:在Logger的构造函数中手动创建新的上下文实例,避免和其他服务共享
public EventsLogger(IConfiguration configuration) { var options = new DbContextOptionsBuilder<DataContext>() .UseSqlServer(configuration.GetConnectionString("MainConnection")) .Options; _dataContext = new DataContext(options); }
- 清空DbContext的跟踪缓存:在TeamsService提交Team修改并查询完Performances后,调用
ChangeTracker.Clear()清空跟踪,但此操作会清除所有实体的跟踪状态,需谨慎使用
await dataContext.SaveChangesAsync(); var performances = await dataContext.Performances.Where(...).ToListAsync(); dataContext.ChangeTracker.Clear();
内容的提问来源于stack exchange,提问作者AlleXyS
相关产品推荐
相关产品推荐

