HelloWorld
HelloWorld

Reputation: 3739

How to get transient DbContext in ASP.NET MVC Core?

I have the following in Startup.cs:

services.AddDbContext<MyDbContext>(options => options.UseSqlite(Configuration.GetConnectionString("Sqlite")));

Now, I'd like to fire up an instance of MyDbContext that has a transient lifetime. The reason for doing so is because I'm populating my cache at startup. So how can I get an instance of MyDbContext that I'm responsible for disposing of myself? I have an IServiceProvider ready at hand.

serviceProvider.GetRequiredService<MyDbContext>();

throws an exception which says it's out of scope.

I understand why the exception is getting thrown, but I'm not sure what the best way of getting around it is.

Upvotes: 2

Views: 926

Answers (2)

Cheeso
Cheeso

Reputation: 192417

joe-audette's answer from 2018 was not clear to me. It relies on serviceProvider and I am not sure where that comes from. Also after creating a scope, it does this:

var scopedServices = scope.ServiceProvider;

...which looks like it is retrieving... a ServiceProvider object with a confusing name (scopedServices). But the ServiceProvider... is the thing it started with.? And then it shows GetRequiredService() but doesn't show how to use the result of that call, and doesn't even show storing the thing it gets. None of that is clear.

If I am writing a Minimal API app using aspnet core, there is no ambient serviceProvider object. There is a WebAppBuilder, and out of the builder I can get an app, of type WebApplication.

Given that, here's what worked for me. (.NET core 8.0)

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
...

var app = builder.Build();

...add all my methods here...

// initialize the in-memory DB just to start out
using (var scope = app.Services.CreateScope())
{
  var serviceProvider = scope.ServiceProvider;
  using (var db = new TodoDb(serviceProvider.GetRequiredService<DbContextOptions<TodoDb>>())) {
    var todo = new Todo("go grocery shopping");
    db.Todos.Add(todo);
    db.SaveChanges();
  }
}

// start listening
var port = Environment.GetEnvironmentVariable("PORT") ?? "8080";
var url = $"http://0.0.0.0:{port}";
app.Run(url);

This article gave me the crucial tip, to create a scope and get the service provider from the scope.

I've got to say, this topic is sort of impenetrable. Most hints I found are partial, obtuse, partly wrong, or out of date.

Upvotes: 0

Joe Audette
Joe Audette

Reputation: 36696

You need to create a scope manually something like this:

using (var scope = serviceProvider.CreateScope())
{
    var scopedServices = scope.ServiceProvider; 
    scopedServices.GetRequiredService<MyDbContext>();

     ...
}

this will give a scoped dbcontext that will get automatically disposed by the scope closure when you are finished using it. During web requests there is a scope created automatically per request so it gets disposed at the end of the request.

Upvotes: 6

Related Questions