ASP.NET Core 嵌套服务架构下DbContext被释放引发ObjectDisposedException问题
嵌套依赖注入下SchoolDbContext触发ObjectDisposedException的成因与解决思路
核心成因
- 基础注册规则对齐性校验:当前Startup中
AddDbContext<SchoolDbContext>默认将DbContext注册为Scoped生命周期,同作用域内解析的AzureService、CompareService也为Scoped,理论上三者生命周期完全对齐,DbContext只会在整个请求作用域结束时被统一释放,不会在业务调用过程中提前触发Dispose。 - 为什么直接把DbContext注入CompareService时程序运行正常?
因为这种注入方式下,CompareService每次被当前请求作用域解析时,拿到的都是和当前作用域绑定的全新DbContext实例,不会跨请求复用已经被释放的旧实例,所以不会触发异常。 - 触发异常的本质是依赖捕获(Captive Dependency):当一个短生命周期的依赖(当前为Scoped的DbContext、Scoped的AzureService)被更长生命周期的服务持有后,长生命周期服务会一直持有这个已经被作用域释放的实例。对应到当前代码的常见触发场景:
- 依赖链中存在Singleton/Transient的服务持有了Scoped的IAzureService:比如CompareService依赖的
IWisaService、ISmartschoolService如果被注册为Singleton,那么它们会永久持有第一个请求作用域内生成的AzureService实例,而这个AzureService持有的DbContext在第一个请求结束时就已经被容器释放,后续调用自然会触发ObjectDisposedException。 - 代码中存在跨作用域手动解析逻辑:比如在后台任务、根容器级别直接解析了IAzureService,这部分代码生成的DbContext在作用域结束时会被自动释放,如果实例被后续请求复用,就会出现已释放的DbContext被调用的问题。
- 代码中手动对DbContext实例调用了
Dispose():比如某次调用结束后没有走容器的标准释放逻辑,主动将注入的DbContext实例标记为已释放,后续业务调用时就会报错。
- 依赖链中存在Singleton/Transient的服务持有了Scoped的IAzureService:比如CompareService依赖的
- 代码疏漏干扰问题定位:当前AzureService的查询方法中catch块使用
throw ex会清空原始异常堆栈,无法直接定位DbContext被释放的具体时机,进一步放大了排查难度。
对应解决思路
- 启动阶段开启容器生命周期校验:构建服务提供商时开启作用域校验和构建校验,启动时就会暴露所有跨生命周期持有依赖的问题,避免运行时才触发随机的释放异常。
- 全链路统一服务生命周期:排查整个依赖链的所有注册项,确保所有和业务逻辑相关的服务(包括
IWisaService、ISmartschoolService、IHelperService)全部注册为Scoped,绝对不要将Scoped服务注入到Singleton服务中。 - 修正异常抛出逻辑:将方法中的
throw ex替换为throw,保留完整异常堆栈,精确定位DbContext被释放的具体调用点。 - 复杂场景兜底方案:如果依赖链层级复杂,无法完全保证生命周期对齐,可以将DbContext的注册方式从
AddDbContext替换为AddDbContextFactory,在AzureService的查询方法内部,通过工厂创建独立的DbContext实例,使用using语法包裹实现用完即释放,从根源上避免跨服务传递DbContext引用导致的提前释放问题。
内容的提问来源于stack exchange,提问作者Yves Laenen
相关产品推荐
相关产品推荐

