从EFCore上下文的使用到深入剖析DI的生命周期最后实现自动属性

2020-01-18 19:39:09王冬梅

但这样做是极其危险的。

为什么危险?到底什么是root provider?那就要从原生DI的生命周期说起。我们知道,DI容器被封装成一个IServiceProvider对象,服务都是从这里来获取。不过这并不是一个单一对象,它是具有层级结构的,最顶层的即前面提到的root provider,可以理解为仅属于系统层面的DI控制中心。在Asp.Net Core中,内置的DI有3种服务模式,分别是Singleton、Transient、Scoped,Singleton服务实例是保存在root provider中的,所以它才能做到全局单例。相对应的Scoped,是保存在某一个provider中的,它能保证在这个provider中是单例的,而Transient服务则是随时需要随时创建,用完就丢弃。由此可知,除非是在root provider中获取一个单例服务,否则必须要指定一个服务范围(Scope),这个验证是通过ServiceProviderOptions的ValidateScopes来控制的。默认情况下,Asp.Net Core框架在创建HostBuilder的时候会判定当前是否开发环境,在开发环境下会开启这个验证:

所以前面那种关闭验证的方式是错误的。这是因为,root provider只有一个,如果恰好有某个singleton服务引用了一个scope服务,这会导致这个scope服务也变成singleton,仔细看一下注册DbContext的扩展方法,它实际上提供的是scope服务:

如果发生这种情况,数据库连接会一直得不到释放,至于有什么后果大家应该都明白。

所以前面的测试代码应该这样写:


  using (var serviceScope = app.ApplicationServices.CreateScope())
   {
     var context = serviceScope.ServiceProvider.GetService<BloggingContext>();
   }

与之相关的还有一个ValidateOnBuild属性,也就是说在构建IServiceProvider的时候就会做验证,从源码中也能体现出来:


if (options.ValidateOnBuild)
      {
        List<Exception> exceptions = null;
        foreach (var serviceDescriptor in serviceDescriptors)
        {
          try
          {
            _engine.ValidateService(serviceDescriptor);
          }
          catch (Exception e)
          {
            exceptions = exceptions ?? new List<Exception>();
            exceptions.Add(e);
          }
        }

        if (exceptions != null)
        {
          throw new AggregateException("Some services are not able to be constructed", exceptions.ToArray());
        }
      }

正因为如此,Asp.Net Core在设计的时候为每个请求创建独立的Scope,这个Scope的provider被封装在HttpContext.RequestServices中。

[小插曲]

通过代码提示可以看到,IServiceProvider提供了2种获取service的方式: