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

为何调用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实例:

  1. TeamsService中先修改并提交了Team实体的更新
  2. 接着查询并加载了Performances实体到DbContext的跟踪缓存中
  3. 当调用EventsLogger的SaveChangesAsync时,DbContext会自动检测所有被跟踪的实体。如果此时Performances实体的某些字段在内存中的值和数据库中的值存在差异(可能是EF Core跟踪时的对比逻辑异常,或者实体属性被意外修改),就会被当作变更提交到数据库。

调换顺序后没问题,是因为先执行日志时,DbContext还未加载Performances实体,SaveChangesAsync只会处理Event实体,不会影响其他表。

解决方案

  1. 禁用Performances实体的跟踪:在查询时添加AsNoTracking(),让DbContext不跟踪这些实体,后续SaveChangesAsync就不会检测它们的变化
var performances = await dataContext.Performances
   .Where(x => x.TeamId == team.Id)
   .AsNoTracking()
   .ToListAsync();
  1. 给Logger服务单独使用新的DbContext:在Logger的构造函数中手动创建新的上下文实例,避免和其他服务共享
public EventsLogger(IConfiguration configuration)
{
    var options = new DbContextOptionsBuilder<DataContext>()
        .UseSqlServer(configuration.GetConnectionString("MainConnection"))
        .Options;
    _dataContext = new DataContext(options);
}
  1. 清空DbContext的跟踪缓存:在TeamsService提交Team修改并查询完Performances后,调用ChangeTracker.Clear()清空跟踪,但此操作会清除所有实体的跟踪状态,需谨慎使用
await dataContext.SaveChangesAsync();
var performances = await dataContext.Performances.Where(...).ToListAsync();
dataContext.ChangeTracker.Clear();

内容的提问来源于stack exchange,提问作者AlleXyS

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 08:05:20