Search code examples
c#asp.net-coreentity-framework-coreasp.net-core-identity

Specifying correct context to add Identity into existing database


Similar questions have been posted before (1, 2, 3), but the situation is different.

I have an .Net Core 7 app using EFCore with some existing entities. Now that everything in there is tested and working, I'd like to add the Identity functionality and then link AspNetUsers to a couple of my existing entities.

Currently there are no identity tables, nor any sort of tables used by the Identity functionality.

Using the Microsoft tutorial Scaffold Identity into a Razor project without existing authorization I get to the step whereby I need to specify my database and user contexts.

After unsuccessfully looking for documentation I entered the database context as being the one which is used in my data project ('EventFinderData' below - a different project to my web app, but within the same solution). I then created a new user context...

enter image description here

After the scaffolding is complete, I get a number of ambiguous reference errors as the scaffolding process creates a new data context:

// This is a duplicate class of that within the EventFinderData project, which is already referenced within my web app
public class EventFinderContext : IdentityDbContext<EventFinderWebAppRazorUser> 
{
    public EventFinderContext(DbContextOptions<EventFinderContext> options)
        : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
        // Customize the ASP.NET Identity model and override the defaults if needed.
        // For example, you can rename the ASP.NET Identity table names and more.
        // Add your customizations after calling base.OnModelCreating(builder);
    }
}

I tried to make both classes partial, but then when I add a migration I get:

More than one DbContext was found. Specify which one to use. Use the '-Context' parameter for PowerShell commands and the '--context' parameter for dotnet commands.

Some experts seem to recommend starting a new project with Identity included already, and building out from there, but its going to be a big job.

  1. What do I need to modify please in order for this scaffolding process to bolt into my existing EFCore logic?
  2. Alternatively is Identity supposed to use a dedicated (separate) context? That would seem unintuitive given the tables need relationships between identity entities and my existing entities.

Update

Based on suggestions, here are the three classes I get after adding the Identity functionality:

Data project (containing Entities and migrations):

EventFinderContext.cs

namespace EventFinderData
{
    public class EventFinderContext : DbContext
    {
        public DbSet<EventItem> EventItems { get; set; }
        public DbSet<EventCategory> EventCategories { get; set; }

        public EventFinderContext(DbContextOptions<EventFinderContext> options) : base(options) 
        {
        }

    }
}

Web app project - Classes created by VS/Identity

Areas\Identity\Data\EventFinderWebAppRazorContext.cs

namespace EventFinderWebAppRazor.Data;
public class EventFinderWebAppRazorContext : IdentityDbContext<EventFinderWebAppRazorUser>
{
    public EventFinderWebAppRazorContext(DbContextOptions<EventFinderWebAppRazorContext> options)
        : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
    }
}

Areas\Identity\Data\EventFinderWebAppRazorUser.cs

namespace EventFinderWebAppRazor.Areas.Identity.Data;

public class EventFinderWebAppRazorUser : IdentityUser
{
}

Program.cs

builder.Services.AddDbContextFactory<EventFinderContext>(options =>
    options.UseSqlServer(
        builder.Configuration.GetConnectionString("EventFinderConnString"), 
        ss => ss.UseNetTopologySuite())
    .EnableSensitiveDataLogging());

builder.Services.AddDefaultIdentity<EventFinderWebAppRazorUser>(
    options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<EventFinderWebAppRazorContext>();

Solution

  • Not sure if something changed (.net6 here) but to add the dot net identity framework to your existing context all you have to do is just provide that context when adding it to the IServiceCollection.

    services.AddIdentity<ApplicationUser, IdentityRole>(options =>
    {
    })
    .AddEntityFrameworkStores<ApplicationDbContext>()
    

    You will need to change your DbContext so it inherits from

    IdentityDbContext<ApplicationUser>
    

    This will only provide the required services, not the different views and UX flows that comes when you scaffold it. So no login/logout, password resets, MFA setup etc...

    I hope this will help.

    Update

    Based on the OP's update, the following updates are required.

    Areas\Identity\Data\EventFinderWebAppRazorUser.cs

    Move this class to your Data project. It will become a part of your own entity model. Make sure that the namespace is also updated. Although this is not really necessary, but it is nice to have all entities in the same namespace.

    Areas\Identity\Data\EventFinderWebAppRazorContext.cs

    Delete this file/class. This is the extra context created by the scaffolding process, but as we want to use our own context we don't need it.

    EventFinderContext.cs

    Update this class like below.

    namespace EventFinderData
    {
        public class EventFinderContext : IdentityDbContext<EventFinderWebAppRazorUser>
        {
            public DbSet<EventItem> EventItems { get; set; }
            public DbSet<EventCategory> EventCategories { get; set; }
    
            public EventFinderContext(DbContextOptions<EventFinderContext> options) : base(options) 
            {
            }
    
        }
    }
    

    Program.cs

    Update this file like below. And make sure all types resolve by updating the usings.

    builder.Services.AddDbContextFactory<EventFinderContext>(options =>
        options.UseSqlServer(
            builder.Configuration.GetConnectionString("EventFinderConnString"), 
            ss => ss.UseNetTopologySuite())
        .EnableSensitiveDataLogging());
    
    builder.Services.AddDefaultIdentity<EventFinderWebAppRazorUser>(
        options => options.SignIn.RequireConfirmedAccount = true)
        .AddEntityFrameworkStores<EventFinderData.EventFinderContext>();