James Gould
James Gould

Reputation: 4712

Dependency Injected DbContext is always null

I've used DI across multiple ASP.NET (Core) applications where DbContext is injected into a controller constructor, and upon the first request to the controller, the constructor is ran and the dependency injected.

I'm now working with a web API project that is hooked into an Azure Message Bus, processing and persisting data from the bus and providing an API endpoint for the data. The class processing and persisting the messages from Azure Message Bus is QueueProcessor.

I need to persist the data from project start, which means I need an instance of DbContext from when the project is ran as opposed to when I'm querying data out via an API endpoint. Due to this, the constructor of QueueProcessor is never implicitly called, and if I want to manually do so I need a pre-existing instance of MyDbContext to pass to it.

I've been researching this for a couple of days, trying out some manual patterns but I'm running into concurrency issues and the whole project feels like a hack at the moment.

The closest question I've come across is this, which despite specifying non-controllers, the accepted answer requires a controller for the dependency to be injected.

This is what I'm currently doing:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddHangfire(x => x.UseSqlServerStorage(Configuration.GetConnectionString("DefaultConnection")));

        services.AddDbContextPool<MyDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("default")));

        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    }

I'm then having to create a new instance of my QueueProcessor and use var context = new MyDbContext() inside the constructor. This is causing concurrency issues and completely negates the DI.

If I want to inject MyDbContext with DI like I have done hundreds of times, creating an instance of the class to run a process inside the QueueProcessor means I have to pass an instance of MyDbContext to the constructor myself. VS flags this up at design time.

I've ever tried botching this as much as possible by doing:

services.AddTransient(x => new QueueProcessor(x.GetService<MyDbContext>(), Configuration.GetConnectionString("MessageBus")));

I'm looking for a cleaner way of achieving:

Is there a way to achieve this or have I completely missed the mark here?

Upvotes: 3

Views: 1798

Answers (1)

James Gould
James Gould

Reputation: 4712

A working solution, not sure if it's the best pattern:

  1. In Startup.cs chance ConfigureServices to return IServiceProvider rather than void.
  2. Build the serviceProvider using: var serviceProvider = services.BuildServiceProvider();
  3. Get the instance of MyDbContext: var context = serviceProvider.GetRequiredService<MyDbContext>();
  4. Create an instance of QueueProcessor and pass context to the constructor and invoke method to begin processing messages.
  5. From ConfigureServices, return your new serviceProvider.

Full code block:

    public IServiceProvider ConfigureServices(IServiceCollection services)
    {
        services.AddDbContext<MyDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("...")));

        var serviceProvider = services.BuildServiceProvider();

        var localMyDbContext = serviceProvider.GetRequiredService<MyDbContext>();

        Processor = new QueueProcessor(localDbContext, Configuration.GetConnectionString("Other"));
        Processor.RunAsync().GetAwaiter().GetResult();

        return serviceProvider;
    }

Upvotes: 1

Related Questions