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).
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>();
…
}
}
…
}
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;
Related
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).
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>();
…
}
}
…
}
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;
I have a scoped service (lets it be UserContext, that contain user IP). It service I try to inject in another services (let's call them ProfileManager and LogerProvider).
In my controller at startup, I added them like so:
service
.AddTransient(ILogerProvider, LogerProvider)()
.AddSingleton<IProfileManager, ProfileManager)()
.AddScoped<IUserContext, UserContext>()
Class LogerProvider contain UserContext inject:
class LogerProvider: ILogerProvider
{
private readonly IUserContext _userContext;
public LogerProvider(IUserContext userContext)
{
_userContext = userContext;
}
}
Class ProfileManager contain LogerProvider inject:
class ProfileManager: IProfileManager
{
private readonly ILogerProvider _logerProvider;
public ProfileManager(ILogerProvider logerProvider)
{
_logerProvider = logerProvider;
}
}
And when i try to run my program i got error:
(Inner Exception #1) System.InvalidOperationException: Error while validating the service descriptor 'ServiceType: IProfileManager Lifetime: Singleton ImplementationType: IProfileManager': Cannot consume scoped service 'IUserContext' from singleton 'IProfileManager'.
I found that i can just change lifetime of ProfileManager just make it transient. But i need to use this service like singleton. So a question: how i can realize dependency injection saved lifetime of services how i typed at the beginning of the text?
You need to manually create a scope and consume it, to do so you'll need to inject the IServiceProvider into your singleton service and then call IServiceCollection.CreateScope
public class ProfileManager : IProfileManager
{
private readonly IServiceProvider _services;
public ProfileManager(IServiceProvider services)
{
_services = services;
}
public void DoSomething()
{
using (var scope = _services.CreateScope())
{
var logger = scope.ServiceProvider.GetRequiredService<ILogerProvider>();
}
}
}
Sidenote: You may want to rethink the lifetimes and scopes of your services if you find yourself repeating this pattern again and again
I would strongly advise against storing any sort of user context information in a singleton class, for security reasons. If you do it wrong, users will be able to see each others' data.
If your logger needs user context information, have the caller pass it in and store it only as a local variable for the duration of the method call. Never store it as a member variable.
class LogerProvider: ILogerProvider
{
public void LogMessage(IUserContext userContext, string message)
{
//Write to logs, including user context information
}
}
This will also prevent anyone from trying to log anything when there is no user context, as it will be obvious to them that they don't have one of the necessary arguments. If your logger has a dependency on user context then its methods should not be called when there isn't one.
If you don't want to burden the caller with the extra argument, consider using the ThreadPrincipal to store your user context.
To be able to use scoped services within a singleton, you must create a scope manually. A new scope can be created by injecting an IServiceScopeFactory into your singleton service (the IServiceScopeFactory is itself a singleton, which is why this works). The IServiceScopeFactory has a CreateScope method, which is used for creating new scope instances.
public class MySingletonService
{
private readonly IServiceScopeFactory _serviceScopeFactory;
public MySingletonService(IServiceScopeFactory serviceScopeFactory)
{
_serviceScopeFactory = serviceScopeFactory;
}
public void Execute()
{
using var scope = _serviceScopeFactory.CreateScope()
var myScopedService = scope.ServiceProvider.GetService<IMyScopedService>();
myScopedService.DoSomething();
}
}
The created scope has its own IServiceProvider, which you can access to resolve your scoped services.
It is important to make sure that the scope only exists for as long as is necessary, and that it is properly disposed of once you have finished with it. This is to avoid any issues of captive dependencies. Therefore, I would recommend:
Only define the scope within the method that you intend to use it. It might be tempting to assign it to a field for reuse elsewhere in the singleton service, but again this will lead to captive dependencies.
Wrap the scope in a using statement. This will ensure that the scope is properly disposed of once you have finished with it.
In short, IServiceProvider.CreateScope() and IServiceScopeFactory.CreateScope() are identical (in non-scoped context even instances of IServiceProvider and IServiceScopeFactory are identical).
But here is a little difference between these abstractions
IServiceProvider's lifetime can be Scoped. But IServiceScopeFactory's lifetime is always Singleton.
Coming from a Java environment I'm having a few issues wrapping my head about the inheritable DBContext class. with Java EE I used to:
Set up one or multiple DB Contexts (if separate clusters of entities where affected);
Added the Context to respective DataAccessor classes that handled the query execution via DI;
Now with EF Core practically all samples I see create an instance of a MyDBContext by calling the default constructor:
using (var db = new myContext()){...}
This raises a few questions for me:
With this Method each DataAccessor class that calls the constructor has its own instance of the Context. Wouldn't it be nicer to only ave one and use DI to inject it when needed?
How do I call the constructor if i didn't overload OnConfiguring(...) to pass the options, but instead used AddDbContext as a Service in Startup.cs? Now the overloaded constructor with the options expects them to be passed on each time the constructor is called.
Is having multiple DBContexts per application/Db even a good practise with EF Core?
Normally you would want single instance per request scope
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
// use options as you would use them in the .OnConfiguring
options.UseSqlServer("your connectionString");
);
}
If you use constructor injection in services, ASP.NET service provider will resolve db context as constructor parameter. All services that have db context this way will share same instance.
public class ServiceDependentOnContext
{
private readonly ApplicationDbContext dbContext;
public ServiceDependentOnContext(ApplicationDbContext dbContext)
{
this.dbContext = dbContext;
}
}
Make sure you configure your service for dependency injection as well
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer("your connectionString")
);
services.AddScoped<ServiceDependentOnContext>();
}
I have defined my DbContext in the Startup.cs
services.AddDbContext<GretaDBContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
I want to access the DbContext in my Dialogs in order to Add and Modify the entities in my database. The problem is my Dialogs are a Singleton service while the DbContext is Scoped so i can't DI it through the constructor.
I've read somewhere I should create a Controller obtaining the DbContext through the IServiceProvider and call Controller functions from the Dialogs but I don't know how to accomplish that.
What is the best way to be able to use the DbContext inside the Dialogs?
You can request a scoped service within a singleton by creating scope and then retrieving the service from an IServiceProvider that is injected into the singleton:
public class DialogsSingleton
{
private readonly IServiceProvider services;
public DialogsSingleton(IServiceProvider services)
{
this.services = services;
}
public void DomeSomethingRequiringDbContext()
{
using (var scope = this.services.CreateScope())
{
var scopedService = scope.ServiceProvider.GetRequiredService<GretaDBContext>();
// Use the scoped service
}
}
}
You should probably create a business logic layer(BLL). You can inject your DbContext into this layer. From there you should be able to inject you BLL into a controller and have your Dialogs reach out. Your controller typically shouldn't contain logic. You may be able to access your BLL from within your dialogs rather than a separate controller.
My textbook shows an example to build identity services, below is the code:
//startup.cs
public void Configure(IApplicationBuilder app) {
app.UseStatusCodePages();
app.UseDeveloperExceptionPage();
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvcWithDefaultRoute();
//try to seed an admin account for the first time the app runs
AppIdentityDbContext.CreateAdminAccount(app.ApplicationServices, Configuration).Wait();
}
//AppIdentityDbContext.cs
public class AppIdentityDbContext : IdentityDbContext<AppUser>
{
public AppIdentityDbContext(DbContextOptions<AppIdentityDbContext> options) : base(options) { }
public static async Task CreateAdminAccount(IServiceProvider serviceProvider, IConfiguration configuration)
{
UserManager<AppUser> userManager = serviceProvider.GetRequiredService<UserManager<AppUser>>();
RoleManager<IdentityRole> roleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
string username = configuration["Data:AdminUser:Name"];
string email = configuration["Data:AdminUser:Email"];
string password = configuration["Data:AdminUser:Password"];
string role = configuration["Data:AdminUser:Role"];
if (await userManager.FindByNameAsync(username) == null)
{
if (await roleManager.FindByNameAsync(role) == null)
{
await roleManager.CreateAsync(new IdentityRole(role));
}
AppUser user = new AppUser
{
UserName = username,
Email = email
};
IdentityResult result = await userManager.CreateAsync(user, password);
if (result.Succeeded)
{
await userManager.AddToRoleAsync(user, role);
}
}
}
}
and then the textbook says:
Because I am accessing a scoped service via the IApplicationBuilder.ApplicationServices provider,
I must also disable the dependency injection scope validation feature in the Program class, as shown below:
//Program.cs
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseDefaultServiceProvider(options => options.ValidateScopes = false)
.Build();
I have a basic understanding in DI, but I'm really confused with this example, below are my questions:
Q1- accessing a scoped service via the IApplicationBuilder.ApplicationServices provider
what does it mean? what services it tries to access? why it is scoped not transient or singleton?
Q2- why we have to disable the dependency injection scope validation, what does scope validation try to achieve?
In order to understand what is going on, you will first have to understand the difference between the dependency injection lifetimes:
Transient: A new instance gets created for every dependency that gets resolved.
Singleton: A single shared instance is used whenever the service gets resolved.
Scoped: A single instance is shared whenever the service gets resolved within a single scope (or request). A subsequent request will mean that a new instance will be created again.
A database context holds a connection to the database. That’s why you usually don’t want it to be a singleton, so that you don’t keep a single connection open for the whole lifetime of your application. So you would want to make it transient. But then, if you needed to access the database multiple times while serving a single request, you would be opening the database connection multiple times within a short duration. So the compromise is to make it a scoped dependency by default: That way you don’t keep the connection open for a long time, but you also can still reuse the connection for a short duration.
Now, let’s think about what happens when a singleton service depends on a non-singleton service: The singleton service gets created just once, so its dependencies are also only resolved once. That means that any dependency it has is now effectively shared throughout the lifetime of that service—which is the lifetime of the application. So by depending on non-singleton services, you effectively make those services quasi-singleton.
That’s why there is a protection in play (during development), that protects you from making this mistake: The scope validation will check that you are not depending on scoped services outside of scopes, e.g. within singleton services. That way, you are not escaping the desired lifetime of that scoped service.
When you now run AppIdentityDbContext.CreateAdminAccount within the Configure method, you are running this outside of a scope. So you are basically within “singleton land”. Any dependency you now create will be kept around. Since you resolve UserManager<AppUser> and RoleManager<IdentityRole> which both depend on the scoped database context, you are now escaping the database context’s configured scoped lifetime.
In order to fix this, you should create a short-lived scope in which you can then access scoped services (since you are within a scope) that will be properly cleaned up when the scope terminates:
public static async Task CreateAdminAccount(IServiceProvider serviceProvider, IConfiguration configuration)
{
// get service scope factory (you could also pass this instead of the service provider)
var serviceScopeFactory = serviceProvider.GetService<IServiceScopeFactory>();
// create a scope
using (var scope = serviceScopeFactory.CreateScope())
{
// resolve the services *within that scope*
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<AppUser>>();
var roleManager = scope.ServiceProvider.GetRequiredService<RoleManager<IdentityRole>>();
// do stuff
}
// scope is terminated after the using ends, and all scoped dependencies will be cleaned up
}