工厂的依赖注入寿命

时间:2020-10-24 20:21:31

标签: c# multithreading asp.net-core .net-core dependency-injection

我具有.Net Core 3.1 Web API,并且具有以下依赖项:

  1. 作用域 SomeDbContext(使用AddDbContextPool)。
  2. 作为瞬态 SomeDbRepository,它表示上述SomeDbContext
  3. 作为瞬态 SomeService,它表示上述SomeDbRepository
  4. Singleton 作为SomeService的工厂。

这里是在Startup.cs中注入依赖项的代码

services.AddDbContextPool<SomeDbContext>(options => options.UseSqlServer("connectionsString"));

services.AddTransient<SomeDbRepository>();// recive SomeDbContext
services.AddTransient<SomeService>();// recive SomeDbRepository

//Factory for SomeService
services.AddSingleton<Func<SomeService>>(provider => () =>
{
    return provider.GetService<SomeService>();
});    

API控制器

//API Controller
public SomeController(Func<SomeService> factory)
{
    _someService = factory.Invoke();
} 

当我在多线程环境中调用我的API时,出现错误:

错误:此操作之前在此上下文上开始了第二次操作 完成。这通常是由使用相同线程的不同线程引起的 DbContext的实例

为什么我的DbContext被不同的线程使用?它假设工厂为每个请求返回一个新的作用域DbContext。我仅一次使用(调用)工厂请求。

如果我将工厂的生命周期从Sinlgeton更改为Scoped,则可以正常工作。但是我不知道为什么?为什么工厂的寿命会受到影响?有人可以解释一下为什么会这样。谢谢大家!

1 个答案:

答案 0 :(得分:2)

Singleton注册的委托人的IServiceProvider参数提供对 root 范围的引用。在解析服务的范围内跟踪范围(和IDisposable瞬态)服务,并且由于根提供者具有自己的范围,因此从根IServiceProvider解析会导致范围(和IDisposable Transient)服务成为在该根提供程序的生存期内进行缓存。通常,这意味着:它们在应用程序运行期间一直存在。

这意味着,在MS.DI容器的上下文中,将通过IServiceProvider参数解析的委托注册为Singleton通常是一个坏主意,因为这会导致已解析的作用域依赖意外地变为单身人士(他们成为偶然的Captive Dependencies)。使用IDisposable瞬态,它变得更加隐含,因为从根提供者那里解决它们甚至可能导致内存泄漏,因为它们只有在应用程序关闭时才会被释放。

虽然仅在解析其他Singleton(或非一次性瞬态)时将委托注册为Singleton是很好的做法,但这通常非常脆弱,因为将来很容易更改它,以免引起麻烦

相反,最好使用MS.DI将工厂代表注册为“范围”:

services.AddScoped<Func<SomeService>>(provider => () =>
{
    return provider.GetService<SomeService>();
}); 

请注意,此建议特定于MS.DI,可能不适用于其他DI容器。