1
votes

I have an app with a common database requirement for an ASP.Net Core web app and an ASP.Net Core Worker service background task. I have a standard Repository pattern based approach with a class for each entity (standard CRUD operations).

My question concerns the best approach to manage the Db context injection into the Repository class for both apps. This is easy for the ASP.Net Core web app: Constructor based DI each time a Db context is required. However, the worker service requires obtaining a fresh Db context for every ExecuteAsync iteration, and passing it to each repository class by some means. This works, but is a bit messy:

code

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            using var scope = _serviceScopeFactory.CreateScope();

            var dbContext = scope.ServiceProvider.GetRequiredService<MyDbContext>();
            
            ...
        }
    }

code

I'm interested in a single Db data access point design pattern that will manage both apps while also getting thread-safe Db contexts when required.

Any help greatly appreciated

1
Using a scoped service-provider is a common way to do it. You wouldn't typically ask the container to create a DbContext, though, and "passing it to each repository class by some means" seems to be missing the point of DI. You only need to ask the container to create an instance of the highest-level type you need (e.g. a service of some sort, or perhaps a repository in your case), and if its dependency graph needs DbContexts, the container will create scoped instances as needed.sellotape
It is not clear to me how to implement what you describe on the Worker Service.TommyBoyWest

1 Answers

1
votes

I have come up with a workable Repository Pattern per entity implementation that works for both the ASP.NET core web app and Worker Service. Here is the code to do this:

public interface IApplianceRepository
{
    Task UpdateApplianceDetailsAsync(Appliance appliance);
    Task CreateApplianceAsync(Appliance appliance);
    …
}
public class SqlApplianceRepository : IApplianceRepository
{ 
    protected IDbContext _db;
    public SqlApplianceRepository() {}

    public SqlApplianceRepository(IDbContext db)
    {
        _db = db;
    }
    public virtual async Task CreateApplianceDetailsAsync(Appliance appliance)
    {
        …
    }
    public virtual async Task UpdateApplianceDetailsAsync(Appliance appliance)
    {
        …
    }
    …
}   
public class SqlApplianceRepositoryWorkerService : SqlApplianceRepository
{
    public override async Task CreateApplianceAsync(Appliance appliance)
    {
        _db = DbContextFactory.GetCurrentDbContext();
        await base.CreateApplianceAsync(appliance);
    }
    public override async Task UpdateApplianceDetailsAsync(Appliance appliance)
    {
        _db = DbContextFactory.GetCurrentDbContext();
        await base.UpdateApplianceDetailsAsync(appliance);
    }
    …
}

Both SqlApplianceRepository and SqlApplianceRepositoryWorkerService are configured as transient services and injected where required in their respective apps. DbContextFactory.GetCurrentDbContext() gets a Db context created for every Worker Service call to ExecuteAsync().