How should I inject a DbContext instance into an IHostedService?
Solution 1
A good way to use services inside of hosted services is to create a scope when needed. This allows to use services / db contexts etc. with the lifetime configuration they are set up with. Not creating a scope could in theory lead to creating singleton DbContexts and improper context reusing (EF Core 2.0 with DbContext pools).
To do this, inject an IServiceScopeFactory
and use it to create a scope when needed. Then resolve any dependencies you need from this scope. This also allows you to register custom services as scoped dependencies should you want to move logic out of the hosted service and use the hosted service only to trigger some work (e.g. regularly trigger a task - this would regularly create scopes, create the task service in this scope which also gets a db context injected).
public class MyHostedService : IHostedService
{
private readonly IServiceScopeFactory scopeFactory;
public MyHostedService(IServiceScopeFactory scopeFactory)
{
this.scopeFactory = scopeFactory;
}
public void DoWork()
{
using (var scope = scopeFactory.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<MyDbContext>();
…
}
}
…
}
Solution 2
You can add create Scope in constructor as below:
public ServiceBusQueueListner(ILogger<ServiceBusQueueListner> logger, IServiceProvider serviceProvider, IConfiguration configuration)
{
_logger = logger;
_reportProcessor = serviceProvider.CreateScope().ServiceProvider.GetRequiredService<IReportProcessor>();
_configuration = configuration;
}
Do add
using Microsoft.Extensions.DependencyInjection;
Related videos on Youtube
Comments
-
Shoe over 3 years
Question
How should I inject (using the standard dependency injection) a
DbContext
instance into aIHostedService
?What have I tried
I currently have my
IHostedService
class take aMainContext
(deriving fromDbContext
) instance in the constructor.When I run the application I get:
Cannot consume scoped service 'Microsoft.EntityFrameworkCore.DbContextOptions' from singleton 'Microsoft.Extensions.Hosting.IHostedService'.
So I tried to make the
DbContextOptions
transient by specifying:services.AddDbContext<MainContext>(options => options.UseSqlite("Data Source=development.db"), ServiceLifetime.Transient);
in my
Startup
class.But the error remains the same, even though, according to this solved Github issue the
DbContextOptions
passed should have the same lifetime specified in theAddDbContext
call.I can't make the database context a singleton otherwise concurrent calls to it would yield concurrency exceptions (due to the fact that the database context is not guaranteed to be thread safe).
-
DavidG over 6 yearsMaybe inject a context factory instead? e.g. stackoverflow.com/a/11748370/1663001
-
-
Shoe over 6 yearsThanks. What's the difference between injecting
IServiceScopeFactory
and injecting anIServiceProvider
directly? -
Martin Ullrich over 6 years
IServiceProvider
will give you only the root service provider. while it also implements the interface to create a scope, you could use it. but the general rule is to request as little as necessary. -
Shoe over 6 yearsIf I spawn a new thread (that will outlive the lifetime of
DoWork
) within theusing
clause and use some of the services that I've fetched in it will it be ok? Or does the scope define when a service gets disposed? -
Martin Ullrich over 6 yearsThe scope will define when things are disposed. be aware that dbcontexts may be reused by another component when using pooling. I only have hosted services code that uses async/await.. like
await Task.Delay(…)
to do long running scheduled work.. that way I can span a using over it easily. -
Shaurav Adhikari about 5 years@MartinUllrich after creating the scope like you mentioned above , I am calling/triggering some services which calls some methods that use the dbcontext. If I have dbcontext added at startup as singleton, will all the scopes create their own instance of dbcontext each or share the same singleton? I am totally confused, I was thinking in order to access the dbcontext from ServiceProvider (like above snippet) you must have added it at Startup, was it singleton or scoped, if it is scoped, how did you achieve that?
-
Martin Ullrich about 5 yearsThe default
AddDbContext()
methods provided by EF register it as scoped only. At the end of the scope, EF will do cleanup for instance. You don't want to have a singleton db context in web apps or all your components would mess with other component's transactions. All services that use db context instances (via constructor injection) need to be scoped as well. -
hosam hemaily over 3 yearsworking only with dbcontext , what about if i need to add service
-
Martin Ullrich over 3 years@hosamhemaily any singleton or transient service can be required directly from the constructor. For any scoped services (like EF does by default or when you call
AddScoped()
on the service collection) you should follow a similar approach creating a new scope inside of the hosted service. If you need to instantiate new instances of a transient service, you can also injectIServiceProvider
directly -
Peter about 3 yearsHi @MartinUllrich, that's really useful, thanks! I've used the suggested pattern in my BackgroundService and everything runs OK. But I'm still confused: my DBContext is wrapped in a repository and I add both the repository and the DBContext to the service collection as singletons in Program.cs' CreateHostBuilder... is my BackgroundService getting it's "own" singleton repository/DBContext via the injected IServiceScopeFactory?
-
Martin Ullrich about 3 years@Peter that should give you the same instance. However I recommend that you use scoped lifetime (or transient if that's not possible) here since EF Core / DbContext is not thread safe and using it from multiple background services or the controllers will likely cause issues
-
user692942 almost 3 years@MartinUllrich is it possible to expand your example to show how this would work when your DbContext is abstracted away in a repository class?
-
lonix about 2 years@MartinUllrich It would be better/easier/safer to depend on
IDbContextFactory<TContext>
instead of the container / service locator. What do you think? (Maybe that wasn't possible when you wrote this answer... I see it was added in v5) -
Martin Ullrich about 2 years@lonix IDbContextFactory is deprecated again and meant for design time purposes. If you resolve the context (or repository as asked before), you'll get the expected result from how EF sets up the injection - e.g. in pooled setups this will take care of using a DbContext from the pool and returning it properly when the scope is disposed at the end of the using block.
-
Martin Ullrich about 2 yearsDo note that this will create a scope and never properly dispose it. While the default implementation AFAIK doesn't dispose of the service if it is ever garbage collected, this might be a problem. This also means that any involved services that are also IDisposable and registered in the scope won't be disposed properly
-
Martin Ullrich about 2 yearsA way to fix it is to also capture the service scope as a field and dispose of it by implementing IDisposable on the hosted service itself to dispose of the created scope.