The application is .NET 5.0 with Blazor server side, Entity Framework Core and AspNetCore.Identity
.
Originally its code for setting up DbContext
and IdentityContext
used to look like this:
// ConfigureServices
if (databaseType == "MySQL")
{
services.AddDbContext<ApplicationDbContext, ApplicationDbContextMySQL>(options =>
{
options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString));
}, ServiceLifetime.Transient);
}
else if (databaseType == "SQLServer")
{
services.AddDbContext<ApplicationDbContext, ApplicationDbContextSQLServer>(options =>
{
options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString));
}, ServiceLifetime.Transient);
}
else if (databaseType == "PostgreSQL")
...
// Identity service configuration
services.AddIdentity<User, UserRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders()
.AddDefaultUI();
// ApplicationDbContext
public class ApplicationDbContext : IdentityContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
{
}
protected ApplicationDbContext(DbContextOptions options) : base(options)
{
}
}
// ApplicationDbContextMysql
public class ApplicationDbContextMySQL : ApplicationDbContext
{
public ApplicationDbContextMySQL(DbContextOptions<ApplicationDbContextMySQL> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
//Identity class
public class IdentityContext : IdentityDbContext<User, UserRole, long>
{
public IdentityContext(DbContextOptions<IdentityContext> options) : base(options)
{
}
protected IdentityContext(DbContextOptions options) : base(options)
{
}
It worked fine for the most part, but there was a caching issue with Entity Framework due to app using Blazor. Everything I read on that issue said that Blazor apps should use AddDbContextFactory
to be able properly maintain DbContext
lifetime, which indeed fixed that issue.
So I switched to using AddDbContextFactory
instead of using AddDbContext
:
if (databaseType == "MySQL")
{
services.AddDbContextFactory<ApplicationDbContextMySQL>(options =>
{
options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString));
});
}
...
...
This introduced error on startup:
Unable to resolve service for type 'ModuleLibrary.Data.ModuleLibraryDbContext' while attempting to activate 'ModuleLibrary.Utils.UserInfoClaimsTransformation'.
According to these two questions, one needs to register dbContext
service on top of the existing AddDbContextFactory
for the ASP.Identity
Using Identity with AddDbContextFactory in Blazor ,
Identity stores with DB Context Factory
So I have changed the above code section to be
if (databaseType == "MySQL")
{
services.AddDbContextFactory<ApplicationDbContextMySQL>(options =>
{
options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString));
});
services.AddScoped<ApplicationDbContext>(p => p.GetRequiredService<IDbContextFactory<ApplicationDbContextMySQL>>().CreateDbContext());
}
This worked fine, but since different dbContext
classes are registered depending on the db provider (ApplicationDbContextMySQL, ApplicationDbContextSQLServer) I'm forced to have it hardcoded to a specific class when I need to use dbContext
in the app - eg ApplicationDbContextMySQL. I am not able to use my dbContext
base class ApplicationDbContext
in the application code.
public UserCacheService(IMemoryCache cache, IServiceProvider serviceProvider, IDbContextFactory<ApplicationDbContextMySQL> dbFactory)
{
...
}
When I try to use the base class ApplicationDbContext
, I get this error:
Unable to resolve service for type 'MyApplication.EntityFrameworkCore.IDbContextFactory`1[MyApplication.Data.ApplicationDbContext
What would be the correct way to register dbFactory
with my generic ApplicationDbContext
class so that it can be used instead of its derived classes like ApplicationDbContextMySQL, ApplicationDbContextSQLServer etc?
You can introduce helper class for such case:
public class DbContextFactoryHelper<TBaseContext, TCurrent> : IDbContextFactory<TBaseContext>
where TBaseContext: DbContext
where TCurrent: TBaseContext
{
private readonly IDbContextFactory<TCurrent> _factory;
public DbContextFactoryHelper(IDbContextFactory<TCurrent> factory)
{
_factory = factory;
}
public TBaseContext CreateDbContext()
{
return _factory.CreateDbContext();
}
}
And register as singleton for concrete ApplicationDbContext
implementation. Singleton because it is default IDbContextFactory
lifetime.
if (databaseType == "MySQL")
{
services.AddDbContextFactory<ApplicationDbContextMySQL>(options =>
{
options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString));
});
// register for each provider accordingly
services.AddSingleton<IDbContextFactory<ApplicationDbContext>, DbContextFactoryHelper<ApplicationDbContext, ApplicationDbContextMySql>>();
}
else // other providers
{
}
// common for all providers
services.AddScoped<ApplicationDbContext>(p => p.GetRequiredService<IDbContextFactory<ApplicationDbContext>>().CreateDbContext());