Answered by:
Dependency injection in HostedService failing

Question
-
User-959394753 posted
Hi,
I am busy on a asp net core application where I need to perform a background task on my database once a day. So reading https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.1&tabs=visual-studio I made a background service that's working fine. But in order to do something with it I need to access my database with the database context. I wanted to use Dependency injection like I do with all my other classes. But then I get a runtime error : HTTP Error 500.30 ANCM In-Process start failure.
The relevant code:
public class BackgroundService : IHostedService, IDisposable { private readonly Data.ApplicationDbContext _DB; private int executionCount = 0; private readonly ILogger<BackgroundService> _logger; private Timer _timer; //public BackgroundService(Data.ApplicationDbContext DB, ILogger<BackgroundService> logger) //this fails!! public BackgroundService( ILogger<BackgroundService> logger) //This works { _logger = logger; // _DB = DB; } ....
Can somebody tell me what I do wrong?
thanks i advance.
Rob
Tuesday, April 14, 2020 3:22 PM
Answers
-
User-474980206 posted
the sample looks pretty simple:
public class BloggingContextFactory : IDesignTimeDbContextFactory<BloggingContext> { public BloggingContext CreateDbContext(string[] args) { var optionsBuilder = new DbContextOptionsBuilder<BloggingContext>(); optionsBuilder.UseSqlite("Data Source=blog.db"); return new BloggingContext(optionsBuilder.Options); } }
then its:
services.AddSingleton<BloggingContextFactory>(new BloggingContextFactory());
then in service:
public BackgroundService( ILogger<BackgroundService> logger, BloggingContextFactory factory)
then when the service needs a context:
using (var dbcontext = factory.CreateDbContext(args)) { }
note: as a Factory is a major concept in DI, you should study the DI pattern (inversion of control)
hint: you should add a connection string to Factory constructor that you can be used by CreateDbContext(). I'd probably use a delegate for the option builder, so it can be set in startup.cs
- Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
Tuesday, April 14, 2020 7:48 PM -
User-854763662 posted
Hi Rob warning ,
You could create scoped service in BackgroundService instead of registering it in Constructor.The doc of Consuming a scoped service in a background task has explained it.
public class BackgroundService : IHostedService, IDisposable { private int executionCount = 0; private readonly ILogger<BackgroundService> _logger; private readonly IServiceProvider _provider; private Timer _timer; public BackgroundService(ILogger<BackgroundService> logger,IServiceProvider provider) //This works { _logger = logger; _provider = provider; } ... private void DoWork(object state) { using (var scope = _provider.CreateScope()) { var dbcontxt = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>(); var data = dbcontxt.Employee.ToList(); } var count = Interlocked.Increment(ref executionCount); _logger.LogInformation( "Timed Hosted Service is working. Count: {Count}", count); } ... }
Best Regards,
Sherry
- Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
Wednesday, April 15, 2020 10:12 AM
All replies
-
User-474980206 posted
how did you register the db context? the constructor only support singleton objects. If you need scoped object, then you need to create a scoped service:
Tuesday, April 14, 2020 3:33 PM -
User-959394753 posted
Hi Bruce, I registered my DB Context in the Startup as follow:
public void ConfigureServices(IServiceCollection services) { ...
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer( Configuration.GetConnectionString("DefaultConnection"))); ...
public void Configure(IApplicationBuilder app,
IHostingEnvironment env,
ApplicationDbContext context,
RoleManager<ApplicationRole> roleManager,
UserManager<ApplicationUser> userManager
)Tuesday, April 14, 2020 4:45 PM -
User-474980206 posted
but that a transient scope (every request gets a unique dbcontext), not a singleton. you should register dbcontext factory as a singleton.
factory:
https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/dbcontext-creation
Tuesday, April 14, 2020 4:52 PM -
User-959394753 posted
OK, I know the services,addSingleton<> but where to put the options??
//services.AddDbContext<ApplicationDbContext>(options => // options.UseSqlServer( // Configuration.GetConnectionString("DefaultConnection"))); services.AddSingleton<Data.ApplicationDbContext>(options => options.UseSqlServer( //Syntax error!! Configuration.GetConnectionString("DefaultConnection")));
What is exactly the correct syntax ??
Tuesday, April 14, 2020 5:27 PM -
User-474980206 posted
for the singleton, you create an instance of an object. depending on your needs, you pass the factory, or you call the factory and pass an dbcontext instance created by the factory. see the factory example link above.
Tuesday, April 14, 2020 5:43 PM -
User-959394753 posted
Sorry Bruce,
It's like Chinese to me.
what is a factory? how do I pass one?
Do I need to change my ApplicationDbContext class?
All I want is to run a Linq query once a day.
I appreciate your help but my level of knowledge is not the same
Tuesday, April 14, 2020 6:01 PM -
User-474980206 posted
the sample looks pretty simple:
public class BloggingContextFactory : IDesignTimeDbContextFactory<BloggingContext> { public BloggingContext CreateDbContext(string[] args) { var optionsBuilder = new DbContextOptionsBuilder<BloggingContext>(); optionsBuilder.UseSqlite("Data Source=blog.db"); return new BloggingContext(optionsBuilder.Options); } }
then its:
services.AddSingleton<BloggingContextFactory>(new BloggingContextFactory());
then in service:
public BackgroundService( ILogger<BackgroundService> logger, BloggingContextFactory factory)
then when the service needs a context:
using (var dbcontext = factory.CreateDbContext(args)) { }
note: as a Factory is a major concept in DI, you should study the DI pattern (inversion of control)
hint: you should add a connection string to Factory constructor that you can be used by CreateDbContext(). I'd probably use a delegate for the option builder, so it can be set in startup.cs
- Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
Tuesday, April 14, 2020 7:48 PM -
User-959394753 posted
Thanks Bruce,
the light is slowly starting to shine.
What I did:
1. I have created a new class RoosterContextFactory, containing:
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Design; using Microsoft.EntityFrameworkCore.Infrastructure; namespace RoosterApp.Data { public class RoosterContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext> { public ApplicationDbContext CreateDbContext(string[] args) { var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>(); optionsBuilder.UseSqlServer("Data Source= Server=xxxxxxxxxxx; Database=xxxxxx; User Id=xxxxxx ; Password = xxxxx"); return new ApplicationDbContext(optionsBuilder.Options); } }
then in my startup I added the line:
services.AddSingleton<RoosterContextFactory>(new RoosterContextFactory());
And then in my BackgroundService where I need to access the database :
using (var dbcontext = RoosterContextFactory.CreateDbContext(args)) //This args argument is not accepted { // I will enter my Linq query here. }
Here, the argument args is not accepted. I have tried to leave it out, or enter an empty string. But it keeps saying "The name 'args' does not exist in the current context".
I have read the note that the args parameter is unused, But it will not build like this. do you have a solution for this?
second question: the database connection string is now copy - past from my Appsettings file. is there a neat way to prevent this and refer to the appsettingsfile directly?
Regards
Rob
Wednesday, April 15, 2020 7:56 AM -
User-854763662 posted
Hi Rob warning ,
You could create scoped service in BackgroundService instead of registering it in Constructor.The doc of Consuming a scoped service in a background task has explained it.
public class BackgroundService : IHostedService, IDisposable { private int executionCount = 0; private readonly ILogger<BackgroundService> _logger; private readonly IServiceProvider _provider; private Timer _timer; public BackgroundService(ILogger<BackgroundService> logger,IServiceProvider provider) //This works { _logger = logger; _provider = provider; } ... private void DoWork(object state) { using (var scope = _provider.CreateScope()) { var dbcontxt = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>(); var data = dbcontxt.Employee.ToList(); } var count = Interlocked.Increment(ref executionCount); _logger.LogInformation( "Timed Hosted Service is working. Count: {Count}", count); } ... }
Best Regards,
Sherry
- Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
Wednesday, April 15, 2020 10:12 AM -
User-959394753 posted
Tanks Cherry,
Your input has directed me into the right direction. The injection of IServiceProvider works as you stated. But the syntax to create a scope does not work this way in core 2.2.
After a lot of reading I came up with the following:
private void DoWork(object state) {using (var scope = ServiceProviderServiceExtensions.CreateScope(_provider)) { var DBcontext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>(); List<Mutation> mut = (from m in DBcontext.Mutations where m.DateStartOn < DateTime.Today where m.ApprovedOn == null select m).ToList(); foreach (Mutation M in mut) ...
Now everything works as it should.
Wednesday, April 15, 2020 2:59 PM