I'm trying to pass EF Core Dbcontext
to my BackgroundService
through dependency injection but somehow cannot get the hang of it.
Can someone tell me what I'm doing wrong and how to do it right/better/easier/another way (not cumbersome if possible)?
I'm using services.AddDbContext<MyDbContext>
to add the DbContext in HostBuilder
, then trying to receive it in the constructor of my BackgroundService
.
I added the DbContext
constructor receiving the DbContextOptions<MyDbContext> options
and passing it to the base constructor of DbContext
in my context, which is the way described in the documentation
Here's the exception I keep getting when starting the application:
System.AggregateException
HResult=0x80131500
Message=Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: Microsoft.Extensions.Hosting.IHostedService Lifetime: Singleton ImplementationType: XXXX.SenderService': Cannot consume scoped service 'XXXX.Entities.MyDbContext' from singleton 'Microsoft.Extensions.Hosting.IHostedService'.)
Source=Microsoft.Extensions.DependencyInjection
StackTrace:
at Microsoft.Extensions.DependencyInjection.ServiceProvider..ctor(IEnumerable`1 serviceDescriptors, ServiceProviderOptions options)
at Microsoft.Extensions.DependencyInjection.ServiceCollectionContainerBuilderExtensions.BuildServiceProvider(IServiceCollection services, ServiceProviderOptions options)
at Microsoft.Extensions.DependencyInjection.DefaultServiceProviderFactory.CreateServiceProvider(IServiceCollection containerBuilder)
at Microsoft.Extensions.Hosting.Internal.ServiceFactoryAdapter`1.CreateServiceProvider(Object containerBuilder)
at Microsoft.Extensions.Hosting.HostBuilder.CreateServiceProvider()
at Microsoft.Extensions.Hosting.HostBuilder.Build()
at XXXX.Program.Main(String[] args) in C:\WORK\REPOS\CRR\Main\XXXX\Program.cs:line 16
This exception was originally thrown at this call stack:
[External Code]
Inner Exception 1:
InvalidOperationException: Error while validating the service descriptor 'ServiceType: Microsoft.Extensions.Hosting.IHostedService Lifetime: Singleton ImplementationType: XXXX.SenderService': Cannot consume scoped service 'XXXX.Entities.MyDbContext' from singleton 'Microsoft.Extensions.Hosting.IHostedService'.
Inner Exception 2:
InvalidOperationException: Cannot consume scoped service 'XXXX.Entities.MyDbContext' from singleton 'Microsoft.Extensions.Hosting.IHostedService'.
Here's the Program.cs:
public static class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).ConfigureLogging(config => config.ClearProviders())
.Build()
.Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((builderContext, services) =>
services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer(builderContext.Configuration
.GetConnectionString(nameof(MyDbContext)))
).AddHostedService<SenderService>())
.UseWindowsService();
}
SenderService.cs:
public class SenderService : BackgroundService
{
public SenderService(IConfiguration configuration, MyDbContext context)
{
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
}
}
and MyDbContext:
public class MyDbContext : DbContext
{
public MyDbContext(DbContextOptions<MyDbContext> options) : base(options) { }
public DbSet<XXXX> XXXX { get; set; }
}
In the end, I figured it out!
What I'm basically doing is using ConfigureServices
to AddScoped
object, which is what happens when AddDbContext<MyDbContext>
is called with only the delegate action filling out the DbContextOptionsBuilder
that is then passed to my MyDbcontext
constructor.
The dog is buried in adding the context as scoped
object, while Service needs it as singleton
to accept it in its constructor.
Well, I can update the piece of code inside the delegate in ConfigureServices
to treat the DbContext as a singleton:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host
.CreateDefaultBuilder(args)
.ConfigureServices((builderContext, services) =>
{
//this adds the dbContext, needs action that accepts DbContextOptionsBuilder
services
.AddDbContext<MyDbContext>(
optionsAction: (optionsBuilder) =>
{
//prepare connection string from IConfiguration existing in HostBuilderContext
//that was passed to ConfigureServices
var connectionString = builderContext.Configuration
.GetConnectionString(nameof(MyDbContext));
//and tell the optionsBuilder to use the SqlServer with the connectionstring
//which is then passed as parameter to MyDbContext constructor
optionsBuilder.UseSqlServer(connectionString);
},
//!!!IMPORTANT!!! - also specify the lifetime of the context, default is Scoped
contextLifetime: ServiceLifetime.Singleton,
optionsLifetime: ServiceLifetime.Singleton)
.AddHostedService<SenderService>()
})
.UseWindowsService();
Or, better, DON'T PASS CONTEXT AS SINGLETON TO THE SERVICE, CREATE IT WHEN NEEDED!
Which I can do for example by passing just the optionsBuilder like this:
// ---------in Program.cs------------
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((builderContext, services) =>
services.AddSingleton((_) => {
//again, get connection string from configuration
var connectionString = builderContext.Configuration
.GetConnectionString(nameof(MyDbContext));
//and return DbContextOptions to be saved as singleton
//to be passed to my service as constructor parameter
return new DbContextOptionsBuilder<MyDbContext>().UseSqlServer(connectionString).Options;
})
.AddHostedService<SenderService>())
.UseWindowsService();
// ---------in SenderService.cs------------
public class SenderService : BackgroundService
{
private readonly DbContextOptions<MyDbcontext> contextOptions;
public SenderService(IConfiguration configuration, DbContextOptions<MyDbContext> dbContextOptions)
{
contextOptions = dbContextOptions;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
....
using(var context = new MyDbContext(contextOptions))
{
....
}
....
}
}
User contributions licensed under CC BY-SA 3.0