Author : MD TAREQ HASSAN | Updated : 2020/09/08
Problem with existing scopes
- DbContext isn’t thread safe and isn’t designed for concurrent use
- Due to stateful nature of blazor server, AddDbContext extension can be problematic because the instance is shared across components within the user’s circuit
- The existing lifetimes are inappropriate for these reasons:
- Singleton shares state across all users of the app and leads to inappropriate concurrent use.
- Scoped (the default) poses a similar issue between components for the same user.
- Transient results in a new instance per request; but as components can be long-lived, this results in a longer-lived context than may be intended.
- Recommended Approach: Component Scope - create DbContext (using
IDbContextFactory
) inOnInitializedAsync()
and disposing inDispose()
(implementingIDisposable
)- Blazor server uses DbContext
- Blazor WebAssembly uses API service in backend
- Links:
AddDbContextFactory in Startup
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
// ... ... ...
//var dbConnectionString = AzureUtil.GetKeyVaultSecretValue(keyVaultUri: Configuration[Constants.Azure.KeyVaultIdentifier], key: DB.ConnectionStringIdentifier);
var dbConnectionString = Configuration["DBConnectionString"];
services.AddDbContextFactory<EmailAddressDbContext>(options => options.UseSqlServer(dbConnectionString));
// ... ... ...
}
DbContext Property and IDisposible in BaseComponent
Components/BaseComponent.cs
using Microsoft.AspNetCore.Components;
using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using AutoMapper;
using System.Threading.Tasks;
public class BaseComponent: ComponentBase, IDisposable
{
// ... ... ...
[Inject]
protected IDbContextFactory<FooDbContext> DbContextFactory { get; set; }
protected FooDbContext DbContext { get; set; }
[Inject]
protected IMapper Mapper { get; set; }
// ... ... ...
protected void InitDependencies()
{
DbContext = DbContextFactory.CreateDbContext();
// setup logger, other things
}
// ... ... ...
public void Dispose()
{
DbContext?.Dispose(); // DbContext will be disposed automatically
}
}
Using DbContext in component
Pages/delete.razor.cs
public class DeleteBase : BaseComponent
{
[Parameter]
public int Id { get; set; }
// ... ... ...
protected override async Task OnInitializedAsync()
{
InitDependencies();
// ... ... ...
// perform async operation
}
protected async Task PerformDeletion()
{
// ... ... ...
try
{
var itemToDelete = await DbContext.Items.FindAsync(Id);
// EF Core Operations
DbContext.Items.Remove(itemToDelete);
await DbContext.SaveChangesAsync();
NavigationManager.NavigateTo("/items?deleted");
}
catch (Exception)
{
// handle exception, show error
}
}
// ... ... ...
}