11

In my Blazor, .Net Core 3.1 server side app, I recently changed the EF contect scoping from transient to using a factory extension and it works well. However, I added the same dbcontext factory code to a second project that uses Identity & I get Exceptions on startup.

InvalidOperationException: Unable to resolve service for type 'OMS.DALInterfaces.Models.OmsDbContext' while attempting to activate 'Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserStore`9

This worked fine when no factory class was in use (ie let DI handle the OMSDbContext)

services.AddDbContext<OmsDbContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")),
            ServiceLifetime.Transient
            );
            
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
         .AddRoles<IdentityRole>()
         .AddEntityFrameworkStores<OmsDbContext>();

Now in the project using Identity I tried:

services.AddDbContextFactory<OmsDbContext>(opt =>
                opt.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))
                .EnableSensitiveDataLogging());

services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
                .AddRoles<IdentityRole>()
                .AddEntityFrameworkStores<OmsDbContext>();

So how do you define Identity in startup when using the Factory extension?

Craig
  • 517
  • 1
  • 9
  • 17

3 Answers3

23

Craig, got it figured out for us.

In ConfigureServices, AddScoped of the DbContext and use the provider to get the factory from the services. Then, return the instance to the provider, as follows. The specs for Identity use scoped so I use scoped here.

        services.AddDbContextFactory<ApplicationDbContext>(options =>
        {
            options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
            options.EnableSensitiveDataLogging();
        });

        services.AddScoped<ApplicationDbContext>(p => p.GetRequiredService<IDbContextFactory<ApplicationDbContext>>().CreateDbContext());
Jason Furr
  • 317
  • 3
  • 2
  • 1
    Nice, thank you! Is this the recommened pattern? I did not find anything on MS docs. – Qrt Nov 26 '20 at 14:29
  • 1
    also curious about the recommended practice here. To me it does not seem to make any difference, getting the context from the factory or the way provided with the answer below? – Allie May 23 '22 at 14:33
  • would also like to know if that practice is recommended. With ApplicationDbContext essentially being added twice, which one of the methods will be used when I try to get DbContext inside my application code? Thanks! – ilya_i Apr 25 '23 at 21:23
  • In the method above, you are not adding it twice. You are adding a database context factory. Then, you are resolving a service by using the database context factory to create a database context. – Jason Furr Apr 26 '23 at 23:02
0

It's because the Identity EntityFrameworkStores assume your DbContext is also available via dependency injection.

What you're doing is adding the factory but not adding your DbContext itself.

You need to add both.

void BuildOptions(DbContextOptionsBuilder options) => options
    .UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))
    .EnableSensitiveDataLogging();

services.AddDbContext<OmsDbContext>(BuildOptions);

services.AddDbContextFactory<OmsDbContext>(BuildOptions);

services.AddDefaultIdentity<IdentityUser>(options => 
            options.SignIn.RequireConfirmedAccount = true)
                .AddRoles<IdentityRole>()
                .AddEntityFrameworkStores<OmsDbContext>();

Peter Csala
  • 17,736
  • 16
  • 35
  • 75
0

Another option that works for me is to use ServiceLifetime.Transient when adding DbContext

public void ConfigureServices(IServiceCollection services){
services
    .AddDbContextFactory<ApplicationDbContext>(options =>
        options.UseSqlServer(connectionString));
services
    .AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(connectionString),ServiceLifetime.Transient);
services
    .AddDefaultIdentity<IdentityUser>(options =>
        options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();
}