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.
Related
New to ASP. Created an application and everything(including db interactions) works fine, but my application should contain background services which are run on startup(and then work until manually stopped). It should have access to dbcontext and ideally load data before any user input.
Seems like it should be created somewhere in ConfigureServices and run in Configure?
Don't really understand how to implement it cause dependency injections. The main problem - I don't understand where and how I can get access to dbcontext. The only way I know is controllers, but it's obviously not the solution.
I know that 100% there is simple solution, but can't find it cause don't know what to search. Some kind of link on reference/Microsoft docs should be enough.
You should register your DbContext in ConfigureServices like so:
Host.CreateDefaultBuilder(args)
ConfigureServices((hostContext, services) =>
{
// Example to add SqlServer DB Context
string connectionString = //for example load connection string from config
services.AddDbContext<MyDbContext>(o => o.UseSqlServer(connectionString));
}
After registering your context like this, you are able to inject it into your other services via constructor injection.
public class MyBackgroundService
{
private readonly IServiceScopeFactory _scopeFactory;
public MyBackgroundServcice(IServiceScopeFactory serviceScopeFactory)
{
_scopeFactory= serviceScopeFactory;
}
public MyData GetData()
{
using IServiceScope scope = _scopeFactory.CreateScope();
MyDbContext context = scope.ServiceProvider.GetService<MyDbContext>();
// Do something with context ...
}
}
Architecture wise I would also suggest implementing a service for your database layer that you can inject in your background services since managing DbContext scopes would be a lot cleaner like this.
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>();
}
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;