How to Inject a Dbcontext Instance into an Ihostedservice

How to consume a Scoped service from a Singleton?

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>();

}
}

}

DbContext dependency injection is conflicting with IHostedService when Application is starting

You can not inject scoped/transiet services to your HostedService, instead you will have to inject IServiceProvider and then create a scope like this:


public class TaskLeituraService : BackgroundService
{
private readonly ILogger<TaskLeituraService> _logger;

public ConsumeScopedServiceHostedService(IServiceProvider services,
ILogger<TaskLeituraService> logger)
{
Services = services;
_logger = logger;
}

public IServiceProvider Services { get; }

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
using (var scope = Services.CreateScope())
{
var _leituraService=
scope.ServiceProvider
.GetRequiredService<LeituraService>();
while (!stoppingToken.IsCancellationRequested)
{
stoppingToken.Register(() => _logger.LogDebug("LeituraTaskService esta parando"));
await ExecuteTaskLeitura(_leituraService);
}
}
await Task.CompletedTask;
}

private async Task ExecuteTaskLeitura(LeituraService _leituraService)
{
try
{
_logger.LogDebug($"{DateTime.Now} - Enviando comando leitura das fotocélulas");
await _leituraService.ExecuteTaskLeituraFotocelulaAsync();
_logger.LogDebug($"{DateTime.Now} - Finalizando comando leitura das fotocélulas");
await Task.Delay(TimeSpan.FromMinutes(5));
}
catch (Exception e)
{
throw e;
}
}
}

Create DBContext outside of DependencyInjection

For resolving a Scoped Service from a Singleton Service, you could create the scoped service from IServiceProvider.

Here is the demo code:

    public class DbHostedService : IHostedService
{
private readonly ILogger _logger;

public DbHostedService(IServiceProvider services,
ILogger<DbHostedService> logger)
{
Services = services;
_logger = logger;
}

public IServiceProvider Services { get; }

public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service is starting.");

DoWork();

return Task.CompletedTask;
}

private void DoWork()
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service is working.");

using (var scope = Services.CreateScope())
{
var context =
scope.ServiceProvider
.GetRequiredService<ApplicationDbContext>();

var user = context.Users.LastOrDefault();

_logger.LogInformation(user?.UserName);
}
}

public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service is stopping.");

return Task.CompletedTask;
}
}

Reference: Consuming a scoped service in a background task

.net core dependency injection to hosted service

The reason you're not allowed to do this is because MyHostedService has a singleton lifetime, which is a longer lifetime than scoped. The basic assumption here is that a service that is registered as scoped should not be kept alive indefinitely, this could easily happen if a singleton service keeps a reference to a scoped service.

I think the solution you're looking for is to inject IServiceProvider into MyHostedService, use it to create a new scope and new XService instance whenever you need it.

That is, replace

_xService.Foo();

with

using(var scope = _serviceProvider.CreateScope()) {
var xService = scope.ServiceProvider.GetService<IXService>();
xService.Foo();
}

An alternative, of course, would be to simply register XService as a singleton, by just replacing the call to AddScoped with AddSingleton, but I would not recommend it.

Edit: I must admit to not reading your link before posting my response. However, I still think this is the most elegant solution.

access context in a hosted service

A Hosted Service is a singleton, which means that only one instance of that class exists for the life of the application.

A Context is scoped, meaning it's designed to have a very short lifespan (only for a specific "scope", like a single HTTP request). It's not good at staying alive indefinitely (there are db connections involved, which you can't guarantee will stay open for the life of the application, for example).

If you inject a Context into another class, the Context will exist for the life of the instance of that class. For a singleton class, that's the life of the application. So this is why you get the exception you do. .NET Core is telling you: "This isn't going to work the way you think it's going to work"

The solution is here: https://stackoverflow.com/a/48368934/1202807

In short, inject a IServiceScopeFactory, which gives you the power to ask the DI engine to give you a scoped class when needed, then it's up to you to keep it around only as long as you need it.

private readonly IServiceScopeFactory _scopeFactory;

public TimedHostedService(ILogger<TimedHostedService> logger, IServiceScopeFactory scopeFactory)
{
_logger = logger;
_scopeFactory = scopeFactory;
}

Then you get your context like this:

using (var scope = scopeFactory.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<Context>();
//do what you need
}//scope (and context) gets destroyed here

Old answer (which is wrong here, but applies to other types of classes):

Just put it in your constructor and it'll get injected by dependency injection:

public TimedHostedService(ILogger<TimedHostedService> logger, Context context)
{
_logger = logger;
_context = context;
}

It's the services.AddDbContext() lines that makes them available for dependency injection. Just pick type you want, since you've defined two:

services.AddDbContext<Context>(options =>
options.UseSqlServer(Configuration.GetConnectionString("LaprDB")));
services.AddDbContext<ContextUsers>(options =>
options.UseSqlServer(Configuration.GetConnectionString("MyDbConnection")));

Access DbContext service from background task

In that case you can only rely on the servicelocater-pattern which is an anti-pattern. Also the DbContext must be instance-per-dependency (transient) rather than scoped.

My suggestion is to inject IServiceScopeFactory which is a singleton and the beginn a scope in your background-worker that does the deed.

using (var scope = _serviceScopeFactory.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<DbContext>();
// now do your work
}

There is no other. These things are not part of the mvc pipeline so they cannot be resolved on the fly. I would also suggest not to directly access the dbcontext somewhere. Wrap things in a repository and then use that repository or even wrap that repository in a so called service class. So your logic is encapsulated and your background-worker just executes things when receiving a message.

Not directly related, but ASP.NET-Core has background-worker built-in with IHostedService.

Edit 2018-07-30: As Steven suggested below, it might not always be an anti-pattern when manually resolving / initiating classes. At least not when the compositionroot is taking care of it. Here is a link he provided, where it is explained quite good.

Edit 2022-07-07:

Using the latest c# syntax and creating an async-scope is now also possible.

await using var scope = _serviceProviderFactory.CreateAsyncScope();
var context = scope.ServiceProvider.GetRequiredService<DbContext>();
// now do your work

Alternatively you can also directly inject IServiceProvider and do the very same thing.

How to inject a reference to a specific IHostedService implementation?

None of the complexity in the other answers is needed as of .net core 3.1. If you don't need to get a concrete reference to your class from another class, simply call:

services.AddHostedService<MyHostedServiceType>();

If you must have a concrete reference, do the following:

services.AddSingleton<IHostedService, MyHostedServiceType>();


Related Topics



Leave a reply



Submit