N层架构Telegram Bot出现无法访问已释放DbContext实例错误求助
问题根本原因
你遇到的上下文已释放错误,是下面几个配置和代码的问题叠加导致的:
- 生命周期不匹配:
ApplicationContext通过AddDbContext注册时默认是Scoped生命周期,但是你把IUnitOfWork、仓储都注册成了Transient,且你的UnitOfWork.Dispose方法主动释放了DI容器托管的ApplicationContext,你手动释放之后,容器后续再尝试操作这个上下文时就会报错 - 异步方法未正确等待:
SaveArticles是异步方法,如果调用链上没有全程加await,会导致数据库操作还没执行完,请求结束后Scoped的上下文就被DI容器释放,等异步操作走到AddAsync/Complete时上下文已经失效 - 异步同步混用:你用了异步的
AddAsync添加数据,但是后续提交调用的是同步的SaveChanges,容易在异步上下文切换时触发提前释放问题
具体修复步骤
1. 移除UnitOfWork中手动释放上下文的逻辑
DI容器会自动管理Scoped服务的释放,不需要你主动释放注入的ApplicationContext,直接删除UnitOfWork中的Dispose实现即可:
// 删掉这行代码 public void Dispose() => _context.Dispose();
如果一定要保留IDisposable实现,仅释放你自己手动创建的非DI托管资源即可,不要触碰DI注入的上下文。
2. 统一异步操作,给UoW新增异步提交方法
避免异步同步混用,同时适配EF Core的异步操作逻辑:
// IUnitOfWork接口新增异步提交方法 public interface IUnitOfWork : IDisposable { INewsRepository News { get; } IUserRepository Users { get; } int Complete(); // 新增 Task<int> CompleteAsync(CancellationToken cancellationToken = default); } // UnitOfWork实现异步提交 public async Task<int> CompleteAsync(CancellationToken cancellationToken = default) { return await _context.SaveChangesAsync(cancellationToken); }
修改SaveArticles方法用异步提交:
private async Task SaveArticles(IEnumerable<NewsDTO> articlesDTO) { var articles = _mapper.Map<IEnumerable<NewsDTO>, IEnumerable<News>>(articlesDTO); await _unitOfWork.News.AddAsync(articles.First()); // 替换同步提交为异步 await _unitOfWork.CompleteAsync(); }
3. 修正依赖注入生命周期匹配
把UoW和仓储的生命周期从Transient改成Scoped,和DbContext的生命周期对齐:
public static class DALInjection { public static void Injection(IServiceCollection services) { // 全部改为Scoped services.AddScoped(typeof(IGenericRepository<>), typeof(GenericRepository<>)); services.AddScoped<IUserRepository, UserRepository.UserRepository>(); services.AddScoped<INewsRepository, NewsRepository.NewsRepository>(); services.AddScoped<IUnitOfWork, UnitOfWork.UnitOfWork>(); } }
4. 检查调用链全程加await
检查ParserService中MakeHtmlRequest方法调用SaveArticles的位置必须加await:
// 示例:MakeHtmlRequest内调用SaveArticles必须加await public async Task<IEnumerable<NewsDTO>> MakeHtmlRequest(int offset, int count) { // 爬取逻辑省略 await SaveArticles(articlesDTO); // 这里的await不能丢 return articlesDTO; }
可选修复(长轮询Bot适用)
如果你的Telegram Bot是用长轮询而非Webhook接收更新,处理逻辑不在HTTP请求的Scoped上下文内,需要用IServiceScopeFactory手动创建Scope获取服务,保证上下文在处理期间不会被释放:
// 处理更新的逻辑示例 private readonly IServiceScopeFactory _scopeFactory; // 构造函数注入IServiceScopeFactory private async Task HandleUpdateAsync(Update update, CancellationToken cancellationToken) { using var scope = _scopeFactory.CreateScope(); var parser = scope.ServiceProvider.GetRequiredService<IParser>(); // 后续用parser执行业务逻辑即可 }
内容的提问来源于stack exchange,提问作者Ilya
相关产品推荐
相关产品推荐

