使用System.Timers.Timer Elapsed事件时DBContext无法获取最新数据
问题
使用结合System.Timers.Timer的代码时,Elapsed事件触发的方法中,通过依赖注入(DI)获取的DBContext无法获取到数据库后台已更新的记录,请问是否需要配置DBContext禁用缓存或进行其他设置?
用户提供的代码:
private System.Timers.Timer aTimer; private Stopwatch stopWatch; //MAIN CODE aTimer = new System.Timers.Timer { Interval = _settings.RetryIntervalsInSeconds * 1000 }; stopWatch = Stopwatch.StartNew(); // Hook up the Elapsed event for the timer. aTimer.Elapsed += async (sender, e) => await VerifyRecordIsUpdatedAsync(sender, e, record.Id); // Have the timer fire repeated events (true is the default) aTimer.AutoReset = true; // Start the timer aTimer.Start(); while (aTimer.Enabled) { var timeRetryExecuting = stopWatch.Elapsed; if (timeRetryExecuting.Seconds > _settings.TimeoutWaitingInSeconds) { aTimer.Stop(); } } // EVENT METHOD private async Task VerifyRecordIsUpdatedAsync(object sender, ElapsedEventArgs e, Guid recordId) { var record = await _context.Records.FirstOrDefaultAsync(x => x.Id == recordId); var timeRetryExecuting = stopWatch.Elapsed; if (record.Status != Status.Pending || timeRetryExecuting.Seconds > _settings.TimeoutWaitingInSeconds) { aTimer.Stop(); stopWatch.Stop(); } }
解决方案
这是因为EF Core的DbContext默认自带一级缓存(上下文缓存)——同一个DbContext实例会缓存已经查询过的实体,后续查询会直接返回缓存里的对象,不会去数据库拉取最新数据。你的代码里_context应该是一个长期存在的实例,所以才会一直读不到更新。
你可以通过以下几种方式解决:
使用
AsNoTracking()绕过缓存
在查询时添加AsNoTracking(),强制每次查询都直接访问数据库,不使用缓存:var record = await _context.Records.AsNoTracking() .FirstOrDefaultAsync(x => x.Id == recordId);刷新指定实体的状态
如果需要保留上下文缓存,但要获取单条实体的最新数据,可以调用ReloadAsync()强制刷新:var record = await _context.Records.FirstOrDefaultAsync(x => x.Id == recordId); if (record != null) { await _context.Entry(record).ReloadAsync(); }使用短生命周期的DbContext
这是最符合EF Core设计理念的方案:DbContext应该是短生命周期的,不要和Timer长期绑定。通过DI注入IServiceScopeFactory,每次在Elapsed事件中创建新的作用域,从中获取DbContext,用完自动释放:// 先在类的构造函数中注入IServiceScopeFactory private readonly IServiceScopeFactory _scopeFactory; // 在事件方法中使用: private async Task VerifyRecordIsUpdatedAsync(object sender, ElapsedEventArgs e, Guid recordId) { using var scope = _scopeFactory.CreateScope(); var scopedContext = scope.ServiceProvider.GetRequiredService<YourDbContext>(); var record = await scopedContext.Records.FirstOrDefaultAsync(x => x.Id == recordId); var timeRetryExecuting = stopWatch.Elapsed; if (record.Status != Status.Pending || timeRetryExecuting.Seconds > _settings.TimeoutWaitingInSeconds) { aTimer.Stop(); stopWatch.Stop(); } }
另外提个小优化:你代码里的while (aTimer.Enabled)是同步阻塞线程的写法,建议改用异步等待或CancellationToken的方式实现超时逻辑,避免不必要的线程占用。
内容的提问来源于stack exchange,提问作者CidoPostan
相关产品推荐
相关产品推荐

