How should I inject a DbContext instance into an IHostedService?

48,150

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;
Share:
48,150

Related videos on Youtube

Shoe
Author by

Shoe

I like C++, Haskell, PostgreSQL, Prolog and you.

Updated on December 24, 2020

Comments

  • Shoe
    Shoe over 3 years

    Question

    How should I inject (using the standard dependency injection) a DbContext instance into a IHostedService?

    What have I tried

    I currently have my IHostedService class take a MainContext (deriving from DbContext) 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 the AddDbContext 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).

  • Shoe
    Shoe over 6 years
    Thanks. What's the difference between injecting IServiceScopeFactory and injecting an IServiceProvider directly?
  • Martin Ullrich
    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
    Shoe over 6 years
    If I spawn a new thread (that will outlive the lifetime of DoWork) within the using 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
    Martin Ullrich over 6 years
    The 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
    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
    Martin Ullrich about 5 years
    The 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
    hosam hemaily over 3 years
    working only with dbcontext , what about if i need to add service
  • Martin Ullrich
    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 inject IServiceProvider directly
  • Peter
    Peter about 3 years
    Hi @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
    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
    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
    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
    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
    Martin Ullrich about 2 years
    Do 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
    Martin Ullrich about 2 years
    A 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.