我具有.Net Core 3.1 Web API,并且具有以下依赖项:
SomeDbContext
(使用AddDbContextPool)。SomeDbRepository
,它表示上述SomeDbContext
。SomeService
,它表示上述SomeDbRepository
。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,则可以正常工作。但是我不知道为什么?为什么工厂的寿命会受到影响?有人可以解释一下为什么会这样。谢谢大家!
答案 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容器。