使用Microsoft.Extensions.DependencyInjection时第二个作用域ScopedOperation构造未触发问题
两个异常现象的本质都来自同一段代码的设计偏差:你在每个作用域块内,始终调用根服务提供器serviceProvider的GetService方法获取Scoped、Transient服务,完全没有使用当前作用域自带的scope.ServiceProvider完成服务解析。
为什么第二个作用域内ScopedOperation的构造函数没有触发
Scoped服务的生命周期绑定逻辑是:实例的存活范围完全由「解析该实例的容器」决定。
当你从根服务提供器(也就是BuildServiceProvider()返回的全局根容器)解析一个Scoped服务时,DI容器不会帮你关联后续手动创建的任何作用域,而是直接把这个Scoped实例缓存到根容器的实例列表中,以等价于单例的形式长期驻留。
你第二次在新作用域内获取ScopedOperation时,依然是从根容器取的缓存实例,自然不会触发构造函数——本质上这个Scoped实例从第一次被解析后就已经变成了全局单例,和你创建的两个作用域没有任何绑定关系。
为什么作用域释放时Scoped、Transient的Dispose方法没有触发
DI容器的实例销毁追踪逻辑是:容器只会负责销毁自己解析出来的实例。
你在作用域块内解析的所有Transient、Scoped服务,都是来自根容器的,这些实例的销毁责任归属根容器,和当前手动创建的scope没有关系。当你调用scope.Dispose()时,作用域容器只会遍历自己缓存的、由自身解析生成的实例列表完成销毁。因为你从未通过作用域容器解析过任何服务,它的追踪列表是空的,自然找不到对应的Scoped、Transient实例去触发Dispose逻辑。
修正方式
只需要把作用域块内的服务获取逻辑,从根容器改为使用当前作用域自带的服务提供器即可:
using (var scope = serviceProvider.CreateScope()) { // 这里使用 scope.ServiceProvider 而非外部的根 serviceProvider var singletonService = scope.ServiceProvider.GetService<SingletonOperation>(); var scopedService = scope.ServiceProvider.GetService<ScopedOperation>(); var transientService = scope.ServiceProvider.GetService<TransientOperation>(); }
修正后符合预期的运行逻辑:
- 每个作用域内首次解析Scoped服务时,都会重新触发构造函数生成新实例,实例的存活范围完全跟随当前作用域
- 每个作用域解析生成的Scoped、Transient实例,都会被作用域容器追踪,在作用域释放时自动调用对应的Dispose方法
- Singleton实例始终只创建一次,由根容器负责销毁
补充:微软官方DI容器默认不会在你从根容器解析Scoped服务时直接报错,这种悄悄把Scoped实例升级为根单例的行为是开发中非常高频的生命周期误区。如果需要拦截这类错误,可以在构建根服务提供器时传入
validateScopes: true参数,容器会直接对这类越界解析抛出明确的异常,避免出现隐性的生命周期混乱。
内容的提问来源于stack exchange,提问作者Camilo Chaves

