Reputation: 963
I have a .Net Core 3.1 web API, and I have the followings dependencies:
SomeDbContext
(using AddDbContextPool).SomeDbRepository
, it recive the above SomeDbContext
.SomeService
, it recive the above SomeDbRepository
.SomeService
.Here the code for injecting the dependencies in 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 Controller
//API Controller
public SomeController(Func<SomeService> factory)
{
_someService = factory.Invoke();
}
When I call my API in a multi thread enviroment I get the error:
Error: A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext
Why is my DbContext used by different threads? It suposse the Factory returns a new scoped DbContext per request. I use (Invoke) the Factory only one time por request.
If I change the lifetime of the Factory, from Sinlgeton to Scoped, it works ok. But I dont know Why? Why the lifetime of the Factory affects? Could someone explain me why it works that way. Thank a lot guys!!
Upvotes: 3
Views: 1514
Reputation: 172606
The IServiceProvider
parameter of the delegate of a Singleton registration provides a reference to the root scope. Scoped (and IDisposable
Transient) services are tracked in the scope they are resolved from and since the root provider has its own scope, resolving from the root IServiceProvider
causes Scoped (and IDisposable
Transient) services to be cached for the lifetime of that root provider. This typically means: they live for as long as the application runs.
What this means is that, in the context of the MS.DI container, it is typically a bad idea to register delegates as Singleton that resolve from the IServiceProvider
parameter, because it causes resolved Scoped depedendencies to accidentally become Singletons (they become accidental Captive Dependencies). With IDisposable
Transients it becomes even more implicit, because resolving them from the root provider could even cause memory leaks, because they will only get released when the application shuts down.
Although it is fine to register a delegate as Singleton when it only resolves other Singletons (or non-disposable Transients), this is typically quite fragile, because it's easy to change this in the future, in a way that accidentally causes trouble.
Instead, with MS.DI, it's best to register your factory delegates as Scoped:
services.AddScoped<Func<SomeService>>(provider => () =>
{
return provider.GetService<SomeService>();
});
Note that this advise is specific to MS.DI and might not be appliable to other DI Containers.
Upvotes: 4