138

How should I inject (using .NET Core's built-in dependency injection library, MS.DI) a DbContext instance into a Singleton? In my specific case the singleton is an 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).

Steven
  • 166,672
  • 24
  • 332
  • 435
Shoe
  • 74,840
  • 36
  • 166
  • 272

3 Answers3

261

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>();
            …
        }
    }
    …
}
Martin Ullrich
  • 94,744
  • 25
  • 252
  • 217
  • 9
    Thanks. What's the difference between injecting `IServiceScopeFactory` and injecting an `IServiceProvider` directly? – Shoe Jan 21 '18 at 21:19
  • 8
    `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. – Martin Ullrich Jan 21 '18 at 22:44
  • 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? – Shoe Jan 22 '18 at 07:36
  • 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. – Martin Ullrich Jan 22 '18 at 07:40
  • @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? – Shaurav Adhikari Mar 01 '19 at 06:06
  • 13
    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. – Martin Ullrich Mar 01 '19 at 07:33
  • working only with dbcontext , what about if i need to add service – hosam hemaily Sep 12 '20 at 09:34
  • @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 – Martin Ullrich Jan 11 '21 at 16:23
  • 1
    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? – Peter Mar 02 '21 at 16:09
  • 2
    @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 – Martin Ullrich Mar 03 '21 at 08:30
  • 1
    @MartinUllrich is it possible to expand your example to show how this would work when your DbContext is abstracted away in a repository class? – user692942 Aug 03 '21 at 14:40
  • 2
    @MartinUllrich It would be better/easier/safer to depend on `IDbContextFactory` 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](https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.idbcontextfactory-1#applies-to)) – lonix Mar 27 '22 at 04:05
  • @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 Mar 27 '22 at 21:20
3

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;
Ganesh Todkar
  • 507
  • 5
  • 12
  • 3
    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 Mar 27 '22 at 21:26
  • 1
    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. – Martin Ullrich Mar 27 '22 at 21:28
3

For the specific case of consuming a DbContext from a singleton service

Since .NET 5 you can register an IDbContextFactory<TContext> for your DbContext and inject it to the singleton services.

Use AddDbContextFactory<TContext> to register a factory:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContextFactory<MyDbContext>(
        options.UseSqlite("Data Source=development.db"));
}

Note: Since .NET 6 you can remove AddDbContext() when using AddDbContextFactory() as the latter also registers the context type itself as a scoped service.

In the singleton service inject IDbContextFactory<TContext> and use CreateDbContext() to create an instance of your DbContext where needed:

public class MySingletonService : BackgroundService, IHostedService
{
    private readonly IDbContextFactory<MyDbContext> _contextFactory;

    public MySingletonService(IDbContextFactory<MyDbContext> contextFactory)
    {
        _contextFactory = contextFactory;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        using (MyDbContext dbContext = _contextFactory.CreateDbContext())
        {
            await dbContext.MyData.ToListAsync(stoppingToken);
        }
    }
}

See also
MS Docs: Using a DbContext factory (e.g. for Blazor)

Remarks
Do not confuse with:
- IDbContextFactory<TContext> from Entity Framework (4.3.1, 5.0.0, 6.2.0).
- IDbContextFactory<TContext> from Entity Framework Core (1.0, 1.1, 2.0, 2.1, 2.2).

Martin Schneider
  • 14,263
  • 7
  • 55
  • 58