I have a class that derives from BackgroundService (IHostedService) for running background tasks. This will be added to my services using builder.Services.AddHostedService<BackgroundTaskService>()
BackgroundService's task runs for the entire duration of the web application, checking for queued data to process.
My question is, how do I instantiate an instance of DbContext from this code?
I could have the BackgroundTaskService constructor accept a DbContext. But wouldn't that keep the DbContext open forever?
And how else could I instantiate it without duplicating all the code to scan my settings file for the connection string, etc.?
The recemmended approach is to inject IDbContextFactory<TContext> as described in the following article: Using a DbContext factory (e.g. for Blazor)
Some application types (e.g. ASP.NET Core Blazor) use dependency injection but do not create a service scope that aligns with the desired DbContext lifetime. Even where such an alignment does exist, the application may need to perform multiple units-of-work within this scope. For example, multiple units-of-work within a single HTTP request.
In these cases, AddDbContextFactory can be used to register a factory for creation of DbContext instances.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContextFactory<ApplicationDbContext>(
options =>
options.UseSqlServer(#"Server=(localdb)\mssqllocaldb;Database=Test"));
}
Then in your controller:
private readonly IDbContextFactory<ApplicationDbContext> _contextFactory;
public MyController(IDbContextFactory<ApplicationDbContext> contextFactory)
{
_contextFactory = contextFactory;
}
public void DoSomething()
{
using (var context = _contextFactory.CreateDbContext())
{
// ...
}
}
You can use scope service factory. Check here for reference.
Here you have an example:
// Injection
public class DataApi : BackgroundService
{
private readonly ILogger<DataApi> logger;
private readonly IServiceScopeFactory scopeFactory;
public DataApi(ILogger<DataApi> _logger, IConfiguration _cfg, IServiceScopeFactory _sSF)
{
logger = _logger;
scopeFactory = _sSF;
// e.g. data from appsettings.json
// var recovery = _cfg["Api:Recovery"];
}
// ...
// Usage
protected async Task DataCollector()
{
logger.LogInformation("Collector");
using (var scope = scopeFactory.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<MyDbContext>();
var myList = await db.MyEntity
.AsNoTracking()
.Where(t => t.active)
.ToListAsync();
if (myList.Count == 0)
{
logger.LogInformation("Empty...");
return;
}
// logic...
}
await Task.CompletedTask;
}
Related
Error Message :A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext
public async Task<UserSearchDto> GetSingle(string userId, string baseUrl)
{
var user =await _userManager.FindByIdAsync(userId);
if (user != null)
{
UserSearchDto userSearches = new UserSearchDto
{
data
};
return userSearches;
}
}
In above service FindByIdAsync throwing this exeption
while i am debugging step by step then i am not facing this error
my setup in startup file as below
services.AddTransient<IAuthService, AuthService>();
Even i changed above service method but its not working
why it requires more time to perform or there is any another issue?
Edit
these manager are passed in service
private readonly UserManager<ApplicationUser> _userManager;
private readonly RoleManager<ApplicationRole> _roleManager;
this is ctor
public AuthService(UserManager<ApplicationUser> _userManager,
RoleManager<ApplicationRole> _roleManager,
IConfiguration configuration) : base(configuration)
{
this._userManager = _userManager;
this._roleManager = _roleManager;
}
User manage and role manager are used from Microsoft.AspNetCore.Identity
services.AddDbContext<Db>(options =>
{
options.UseSqlServer(mySqlConnectionStr);
}
);
The DbContext has a scoped service lifetime, coupled to an asp.net request. Thus services using the context should preferably also have a scoped service lifetime.
I can recommend you such approach (TModel can be yours UserSearchDto):
// Or your db context directly in class but this is better
private readonly IServiceScopeFactory _factory;
public async Task<TModel> FindByIdAsync(ulong id)
{
using var scope = _factory.CreateScope();
// your context gets here
await using var userManager = scope.ServiceProvider.GetRequiredService<UserManagerContext>();
// this is important
var entities = userManager.Set<TModel>().AsNoTracking();
// data should be filled after FindByIdAsync(ulong id), not in this method
return await entities.FirstOrDefaultAsync(t => t.Id == id);
}
I have a razor pages with some classes such as scheduled tasks that run in the background. I have a IUnitofWork for the databases and uses EF.
In my schedule class "WorkerService : BackgroundService" it does routine backups and other tasks.
How can I reference the Database because I dont have DI due to not implementing razor pages?
Usually this is how I do it using DI on razor code files:
private readonly IUnitOfWork _unitOfWork;
public IndexModel(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
I am new to this DI and been in the udemy and microsoft site daily. I think I have to create a IUnit of work and pass in the ApplicationDbContext maybe in an ovveride? But how to get the context without DI.
Program.cs
builder.Services.AddHostedService<WorkerService>(); //Uses cronos to execute DoWork() every hour
WorkerService.cs
private const string schedule = "*/5 * * * *"; // every 5 for testing
private readonly CronExpression _cron;
public WorkerService()
{
_cron = CronExpression.Parse(schedule);
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var utcNow = DateTime.UtcNow;
var nextUtc = _cron.GetNextOccurrence(utcNow);
await Task.Delay(nextUtc.Value - utcNow, stoppingToken);
await DoBackupAsync();
}
}
private static Task DoBackupAsync()
{
DoWork d = new DoWork();
return Task.FromResult("Done");
}
RazorApp/Pages
This is where I need to save data
RazorApp/ScheduledTasks/DoWork.cs
RazorApp/ScheduledTasks/WorkerService.cs
Attempting to DI either the IUnitOfWork or ApplicationDbContext
Further trying different examples like: https://dotnetcorecentral.com/blog/background-tasks
Results in this error as well: Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: Microsoft.Extensions.Hosting.IHostedService Lifetime: Singleton ImplementationType: WebRazor.ScheduledTasks.BackgroundPrinter': Cannot consume scoped service 'WebRazor.DataAccess.ApplicationDbContext' from singleton 'Microsoft.Extensions.Hosting.IHostedService
public BackgroundPrinter(ILogger<BackgroundPrinter> logger, IWorker worker, ApplicationDbContext dbContext)
{
this.logger = logger;
applicationDbContext = dbContext;
}
Is this where I need to get it from the settings directly or is there a slick way to grab the Db Context?
Ok, wow I was all over the place. I implemented this and it worked.
public BackgroundPrinter(ILogger<BackgroundPrinter> logger, IWorker worker, IServiceProvider serviceProvider)
{
this.logger = logger;
unitOfWork = serviceProvider.CreateScope().ServiceProvider.GetRequiredService<IUnitOfWork>();
}
On construct pass in the Iserviceprovider and access this way. DI to the DoWork and its writing to the database.
Still not sure this is the best way to solve this problem, but I think I have to refactor my unitofwork because every now and then I get an error on writing
"A second operation was started on this context instance before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext" but this may be easy to solve.
The background tasks with hosted services doc describes how to consume a scoped service in a background task:
Inject IServiceProvider in hosted service's ctor
Use the IServiceProvider to create scope during the execution
Use the scope to resolve required services
Personally for "timed" services like provided WorkerService in most cases I found useful to create scope per every iteration (especially if it uses EF internally). Something along this lines:
private readonly IServiceProvider _serviceProvider;
public WorkerService(IServiceProvider serviceProvider)
{
...
_serviceProvider = serviceProvider;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var utcNow = DateTime.UtcNow;
var nextUtc = _cron.GetNextOccurrence(utcNow);
await Task.Delay(nextUtc.Value - utcNow, stoppingToken);
await DoBackupAsync(stoppingToken);
}
}
private static async Task DoBackupAsync(CancellationToken stoppingToken)
{
using (var scope = _serviceProvider.CreateScope()) // do not forget to dispose the scope
{
var backupService = scope.ServiceProvider.GetRequiredService<IBackupService>();
await backupService.BackUp(stoppingToken);
}
}
I have problem with ef core. I have two services which read data from database. On one page is call first service and on second page is called second service. When i click to button for create a new program i got error. I call it normally from page with inject service. Can anybody help me with it?
Show in application
builder.Services.AddDbContextPool<Context>(options =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("Connection"));
});
TestService1:
public class TestService1 : ITestService1
{
private readonly Context _context;
private readonly IMapper _mapper;
public TestService1(Context context, IMapper mapper)
{
_kreativgangContext = kreativgangContext;
_mapper = mapper;
}
public virtual async Task<AllProgramViewModel> HandleAsync(AllProgramFilterViewModel filter)
{
var model = new AllProgramViewModel();
var data = _context.Programs.Where(x => (EF.Functions.Like(x.Name ?? "", "%" + filter.Name + "%") || string.IsNullOrEmpty(filter.Name)))
.Select(x => new Core.Models.Program() { ID = x.ID, Name = x.Name, Order = x.Order });
result.Model.TotalCount = await data.CountAsync();
result.Model.Items = data.Select(x => _mapper.Map<AllProgramItemViewModel>(x));
return model;
}
}
public interface ITestService1
{
public Task<AllProgramViewModel> HandleAsync(AllProgramFilterViewModel filter);
}
Test service 2:
public class TestService2 : ITestService2
{
private readonly Context _context;
public TestService2(Context context)
{
_context = context;
}
public virtual async Task<NewProgramViewModel> HandleAsync()
{
var model = new NewProgramViewModel();
List<ProgramOrderViewModel> items = _context.Programs.Select(x => new Core.Models.Program() { Order = x.Order, ID = x.ID })
.Select(x => new ProgramOrderViewModel()
{
ID = x.ID,
Order = x.Order
}).ToList();
return await Task.FromResult(model);
}
}
public interface ITestService2
{
public Task<NewProgramViewModel> HandleAsync();
}
Error:
Error: System.InvalidOperationException: A second operation was started on this context instance before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.
at Microsoft.EntityFrameworkCore.Infrastructure.Internal.ConcurrencyDetector.EnterCriticalSection()
at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.Enumerator.MoveNext()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
at Mitar.Kreativgang.Admin.Handlers.TestService2.HandleAsync() in D:\Programming\Kreativgang\Src\Mitar.Kreativgang.Admin\Handlers\TestService2.cs:line 26
at Mitar.Kreativgang.Admin.Pages.Program.ProgramNew.OnInitializedAsync() in D:\Programming\Kreativgang\Src\Mitar.Kreativgang.Admin\Pages\Program\ProgramNew.razor:line 114
at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()
This is a known and documented pitfall, explained in ASP.NET Core Blazor Server with Entity Framework Core (EFCore). In Blazor Server, the DI scope is the user circuit - essentially the user session. That means that a scoped service like TestService2 or a DbContext will remain in memory for a long time and end up reused by multiple methods and actions.
As the docs explain :
Blazor Server is a stateful app framework. The app maintains an ongoing connection to the server, and the user's state is held in the server's memory in a circuit. One example of user state is data held in dependency injection (DI) service instances that are scoped to the circuit. The unique application model that Blazor Server provides requires a special approach to use Entity Framework Core.
You need to register and use a DbContextFactory (or PooledDbContextFactory) instead of a DbContextPool, and create a new DbContext instance right where it's used.
builder.Services.AddDbContextFactory<ContactContext>(opt =>
opt.UseSqlServer(...));
or
builder.Services.AddPooledDbContextFactory<ContactContext>(opt =>
opt.UseSqlServer(...));
The service constructors should accept the factory instead of a context :
public TestService2(AddDbContextFactory<ContactContext> factory)
{
_factory = factory;
}
public virtual async Task<NewProgramViewModel> HandleAsync()
{
using var context=_factory.CreateContext())
{
...
}
}
Component Scope
To limit a DbContext's scope to a single component it's not enough to just inject the DbContextFactory. The DbContext instance needs to be explicitly disposed when the user navigates away from the component. To do this, the component needs to implement IDisposable. This is explained in the section Scope to the component lifetime
#implements IDisposable
#inject IDbContextFactory<ContactContext> DbFactory
...
#code
{
ContactContext? Context;
public void Dispose()
{
Context?.Dispose();
}
protected override async Task OnInitializedAsync()
{
Context = DbFactory.CreateDbContext();
...
}
}
I have created a .Net Core MVC project and understand that how the dependency injection works for our MVC controller as shown below, but same like I wanted to create an object for my own class by calling the same injected interface/class as a parameter.
public class ShiftsController : BaseController
{
ShardingDbContext _dbContext;
public ShiftsController(ShardingDbContext ShardingDbContext) : base(ShardingDbContext)
{
_dbContext = ShardingDbContext;
ViewBag.Menu = BuildMenu();
}
I have injected the DbContext into my Startup.cs file as below,
//Entity Framework Core
services.AddDbContext<ShardingDbContext>(options => options.UseSqlServer(ConnectionString),
ServiceLifetime.Transient);
The ShiftsController is a C#-MVC controller and the DbContext is working perfectly when I run my app and go to Shift's page in my application, but when I try like below-given code, it's not working and gives an error. So I don't know how to pass the registered class's object while creating an object by using "new" keyword.
public class JobScheduler
{
ShardingDbContext _dbContext;
public JobScheduler(ShardingDbContext ShardingDbContext)
{
_dbContext = ShardingDbContext;
}...
This is my own class and tried to create an object for the class JobScheduler as shown below.
JobScheduler jobs = new JobScheduler();
So now I don't know how to pass the EF core's DbContext's object to the constructor JobScheduler, the DI works fine for the controller but not for a normal class. Can anyone help with this and I am eagerly waiting to understand this logic as well?.
Register your JobScheculer like this:
services.AddSingleton<JobScheduler>();
then use your dbContext like this:
public class JobScheduler
{
private readonly IServiceProvider provider;
public JobScheduler(IServiceProvider provider)
{
}...
public (or private etc) DoYourJob()
{
using (var scope = provider.CreateScope())
{
var dbContext = scope.GetService<ShardingDbContext>();
//use it here
}
}
At the end of the ConfigureServices method of the Startup.cs class, and I did not change anything in the JobSchedulerclass and passing the DbContext object from the service provider as shown below, thanks to everyone who tried to help with this question.
public void ConfigureServices(IServiceCollection services)
{
...
...
JobScheduler job = new
JobScheduler(services.BuildServiceProvider().CreateScope().ServiceProvider
.GetService<ShardingDbContext>());
job.Start();
}
You are right: Your DI works fine but your ShardingDbContext is not passed into your JobScheduler because you are not using DI to instanciate JobScheduler. Whenever you are explicitly creating an object instance using the new keyowrd you are not using DI.
You have two options:
Wherever you are calling new JobScheduler() let DI inject you a ShardingDbContext through the constructor and pass it to JobScheduler like so new JobScheduler(shardingDbContext)
Register JobScheduler to the dependency injection as well and let DI build up the whole chain so you don't need to call new JobScheduler() but rather get a JobScheduler injected directly wherever you need it
Edit
As requested here is the example for a timed job using a short lived DB context:
public class TimedBackgroundService : IHostedService, IDisposable
{
private readonly Timer timer;
private readonly IServiceProvider serviceProvider;
public TimedBackgroundService(IServiceProvider serviceProvider)
{
timer = new Timer(async state => await ExecuteAsync());
this.serviceProvider = serviceProvider;
}
public Task StartAsync(CancellationToken stoppingToken)
{
timer.Change(0, TimeSpan.FromMinutes(30));
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken stoppingToken)
{
timer.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose() => timer.Dispose();
private async Task ExecuteAsync()
{
try
{
using var scope = serviceProvider.CreateScope();
var job = scope.ServiceProvider.GetRequiredService<MyJob>();
await job.Execute();
}
catch (Exception exception)
{
// log error here
return;
}
}
}
The MyJob class wil look something like this:
public class MyJob
{
private readonly ShardingDbContext dbContext;
public MyJob(ShardingDbContext dbContext)
{
this.dbContext = dbContext;
}
public Task Execute()
{
// Your logic goes here
}
}
Then you register your classes in the startup like so:
services
.AddHostedService<TimedBackgroundService>()
.AddScoped<MyJob>();
Now you have a job which runs every 30 minutes and uses a short lived db context.
In my project, I have a static converter method to convert client and database objects into each other. One of those static methods needs to access the database. Before introducing dependency injection into my project, that was quite simple:
internal async static Task<ViewerColumn> FromClientColumn(ViewerColumnSettings col) {
using MpaContext db = new MpaContext();
return new ViewerColumn() {
// ...
SourceColumnID = await db.SourceColumns
.Where(sc => sc.Key == col.DataField)
.Select(sc => sc.ID)
.SingleAsync()
};
}
I want to change this by introducing dependency injection project-wide. My first approach was to simply add the database context as a separate parameter:
internal async static Task<ViewerColumn> FromClientColumn(ViewerColumnSettings col, MpaContext context) {
using MpaContext db = context;
// ...
}
This, however, leads to problems, if the context from the parameter gets disposed somewhere else. So my idea was to dependency-inject the context to the class inself. This, however, doesn't work, because you obviously can't use parameters for static constructors.
Here's how the method is called (currently with the context parameter):
// Controller method with dependency injection
[HttpPut("ViewerRoles/{vrID}")]
public async Task<ActionResult> UpdateViewSettings(int vrID, ViewerRoleSettings updatedData) {
using MpaContext db = _mpaContext;
await storedViewerRole.ApplyViewerRoleSettingsAsync(updatedData, _mpaContext);
}
// ViewerRole.cs
internal async Task ApplyViewerRoleSettingsAsync(ViewerRoleSettings updatedData, MpaContext context) {
// Create new entries
foreach (Client.ViewerColumnSettings col in updatedData.ViewerColumns) {
ViewerColumns.Add(await ViewerColumn.FromClientColumn(col, context));
}
}
This approach fails, because the context gets disposed in UpdateViewSettings and in FromClientColumn.
What's the best-practice approach for such a case? I could dispose the context only, if it wasn't open beforehand, but that sounds stupid to me.
Dependency Inversion / Dependency Injection does not play well with static.
Make an abstraction and derived implementation with injected context
public class ViewerColumnService : IViewerColumnService {
private readonly MpaContext db ;
public ViewerColumnService (MpaContext db) {
this.db = db;
}
public async Task<ViewerColumn> FromClientColumn(ViewerColumnSettings col) {
return new ViewerColumn() {
// ...
SourceColumnID = await db.SourceColumns
.Where(sc => sc.Key == col.DataField)
.Select(sc => sc.ID)
.SingleAsync()
};
}
}
Register this new service and explicitly inject it where it is needed. Stop manually disposing of the context by wrapping it in a using statement. Let the DI container handle the lifetime of the components.