This is a follow-on from a question I asked the other day. I have an AppDbContext, that extends the DbContext class, and is used in most projects in my solution. I want to add a BlazorAppDbContext class (that extends AppDbContext) to my Blazor server-side project, and add in some Blazor-specific code. My problem was working out how to configure the options to be passed in.
Kudos to Neil W, who walked me through this. I ended up with the following in Program.cs...
DbContextOptionsBuilder<AppDbContext> b = new();
b.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"));
b.EnableSensitiveDataLogging();
b.EnableDetailedErrors();
builder.Services.AddTransient(sp =>
new BlazorAppDbContext(b.Options, sp.GetService<AuthenticationStateProvider>()));
That works fine for when I'm doing plain injection, ie decorating a property with the [Inject] attribute. However, it doesn't help me when I want to use a factory to create the context. See new BlazorAppDbContext(b.Options, sp.GetService
The code for injecting a regular AppDbContext factory looked like this...
builder.Services.AddDbContextFactory<AppDbContext>(lifetime: ServiceLifetime.Scoped);
However, substituting BlazorAppDbContext instead suffers from the same problem that motivated my previous question, namely that I get "System.AggregateException: 'Some services are not able to be constructed" when it tries to create the service.
I thought of trying to mimic the code Neil W showed me, but couldn't work out what to create. When we inject a factory, we ask for an object of type IDbContextFactory<MyDbContext>, so I tried that...
DbContextOptionsBuilder<AppDbContext> b = new();
b.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"));
b.EnableSensitiveDataLogging();
b.EnableDetailedErrors();
builder.Services.AddTransient(sp => new BlazorAppDbContext(b.Options, sp.GetService<AuthenticationStateProvider>(), sp.GetService<IHttpContextAccessor>()));
builder.Services
.AddTransient<IDbContextFactory<BlazorAppDbContext>>(sp
=> new DbContextFactory<BlazorAppDbContext>(sp,
b.Options,
new DbContextFactorySource<BlazorAppDbContext>()));
...where b is the DbContextOptionsBuilder that Neil W mentioned, extracted into a separate variable so I can use it in both cases.
However, this gave me a compiler error... "Argument 2: cannot convert from 'Microsoft.EntityFrameworkCore.DbContextOptions<AppDbContext>' to 'Microsoft.EntityFrameworkCore.DbContextOptions<BlazorAppDbContext>'".
Anyone any idea how I do this? Thanks
CodePudding user response:
Actually the answer to your previous question led you in the wrong direction.
Take a look at the DbContext class (or one of the IdentityDbContext classes) constructors. There is no requirement for TContext constructor having generic DbContextOptions<TContext> parameter - the non generic DbContextOptions is enough. The generic one is provided just for type safety (there is runtime check inside base constructor if options.ContextType == GetType()).
So, the proper solution is to (1) have your derived context public constructor receive DbContextOptions<TDerivedContext>, while (2) the public constructor of the based context still receive DbContextOptions<TBaseContext>, but also (3) add protected constructor receiving non generic DbContextOptions in the base class and call it from the other two, e.g.
public class AppDbContext : IdentityDbContext<User>
{
// (2)
public AppDbContext(DbContextOptions<AppDbContext> options)
: this(options) { }
// (3)
protected AppDbContext(DbContextOptions options)
: base(options) { }
// ...
}
public class BlazorAppDbContext : AppDbContext
{
private readonly AuthenticationStateProvider? _authenticationStateProvider;
// (1)
public BlazorAppDbContext(
DbContextOptions<BlazorAppDbContext> options,
AuthenticationStateProvider? authenticationStateProvider)
: base(options) => _authenticationStateProvider = authenticationStateProvider;
// ...
}
This solves the compilation error while keeping the type safety.
Now you can use the usual AddDbContext / AddDbContextFactory calls, e.g.
builder.Services.AddDbContextFactory<BlazorAppDbContext>(options =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"));
options.EnableSensitiveDataLogging();
options.EnableDetailedErrors();
}, lifetime: ServiceLifetime.Scoped);
Note that registering IDbContextFactory<TContext> also registers TContext for you, so there is no need having both AddDbContextFactory and AddDbContext.
