I am building a Blazor Server front end to an existing domain layer. This layer offers various injectable services to do modifications on the EF Core repository. For this, the services itself requests a DbContext from the (standard Microsoft) DI container. This works fine with regular MVC.NET/Razor pages with a scoped DbContext instance, but as documented, this is problematic with Blazor. In a Blazor Server app we’d want to use DbContextFactory to generate short-lived DbContext instances for operations instead.
It’s no problem to have both a DbContext and DbContextFactory in the same application, but I’m struggling to understand how to adapt my services. Or if I even need to? To illustrate, this is the current code:
My Page:
@page “/items”
@inject ItemService ItemService
// somewhere in the code
ItemService.DoOperation(…)
My service
class ItemService
{
public ItemService(MyDbContext myDbContext)
{
…
}
public bool DoOperation(…)
{
…
_myDbContext.SaveChanges();
}
}
Startup.cs:
services.AddDbContext<MyDbContext>(options => …),
contextLifetime: ServiceLifetime.Transient,
optionsLifetime: ServiceLifetime.Singleton
);
services.AddDbContextFactory<MyDbContext>(options => …);
I’ve changed the lifetimes for DbContext according to the example given in this answer and so far I haven’t been able to create any issues, but I don’t fully understand the lifetime issues at play here. How can I engineer my service to play well in both a Blazor and a MVC/Razor Pages application in an obvious way?
In your typical MVC application, one request represents a single unit of work. The DbContext is generated as a scoped service and injected through the constructor. This is fine.
On the other hand, in Blazor Server one request no longer represents a single unit of work. The first request creates a circuit which means that any scoped service injected is going to have a lifetime as described here.
In Blazor Server apps, a unit of work is a SignalR message. (a button click for example that adds a new row to the database). Because of this, injecting your context directly is not the way to go.
That's why Blazor Server has IDbContextFactory<T>. Initialize it like this:
services.AddDbContextFactory<DbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("WebDB")));
In your Razor Components (tied to the Blazor app) you use it like so:
private readonly IDbContextFactory<DbContext> factory;
public Component(IDbContextFactory<DbContext> f)
{
factory = f;
}
public void Click()
{
using(DbContext cnt = factory.CreateDbContext())
{
// Your code here
}
}
This is further explained here.
Documentation: ASP.NET Core Blazor Server with Entity Framework Core (EFCore).
The problem is transitive: Your services rely on a should-be-scoped resource and that only works when you register those services as scoped as well. Which you can't.
The proper way is to rewrite your services to the DbContext per Operation model, and inject the DbContextFactory.
It looks like you already have a mixed model (with a SaveChanges per operation they are actually a UoW).
When you don't want to make those changes you could weasel your way out by registering the DbContext as Transient. That feels bad but it's designed to quickly release the underlying connection. So it's not the resource leak that it looks like.