Search code examples
c#asp.netentity-frameworkasp.net-identity

Entity Framework 7 composite keys in migrations


I'm trying MVC 6, Entity Framework 7 and ASP.NET Identity for the first time. I just generated a default project and I'm changing the login process to support accounts by tenant, based on the following tutorial.

https://www.scottbrady91.com/ASPNET-Identity/Quick-and-Easy-ASPNET-Identity-Multitenancy

The thing is, when I try to change the initial migration to support a composite key (Id,TenentId) on the AspNetUsers table it simply fails with a generic message. The migration code is the following.

migrationBuilder.CreateTable(
                name: "AspNetUsers",
                columns: table => new
                {
                    Id = table.Column<string>(nullable: false),
                    TenantId = table.Column<string>(nullable: false),
                    AccessFailedCount = table.Column<int>(nullable: false),
                    ConcurrencyStamp = table.Column<string>(nullable: true),
                    Email = table.Column<string>(nullable: true),
                    EmailConfirmed = table.Column<bool>(nullable: false),
                    LockoutEnabled = table.Column<bool>(nullable: false),
                    LockoutEnd = table.Column<DateTimeOffset>(nullable: true),
                    NormalizedEmail = table.Column<string>(nullable: true),
                    NormalizedUserName = table.Column<string>(nullable: true),
                    PasswordHash = table.Column<string>(nullable: true),
                    PhoneNumber = table.Column<string>(nullable: true),
                    PhoneNumberConfirmed = table.Column<bool>(nullable: false),
                    SecurityStamp = table.Column<string>(nullable: true),
                    TwoFactorEnabled = table.Column<bool>(nullable: false),
                    UserName = table.Column<string>(nullable: true)                    
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_ApplicationUser", x => new { x.Id, x.TenantId });
                });

I also tried the following code but it also doesn't work.

constraints: table =>
                    {
                        table.PrimaryKey("PK_ApplicationUser", x => x.Id);     
                        table.PrimaryKey("PK_ApplicationUser", x => x.TenantId);    
                    });

If I leave it as a non composite key the migration executes successfully.

constraints: table =>
                {
                    table.PrimaryKey("PK_ApplicationUser", x => x.Id);
                });

I tried to define a unique constraint over both columns but I wasn't able to find a way to do it.

Does anyone have any thoughts on this?


Solution

  • Any customization of the Identity tables needs to be done by creating derived classes of the Identity model classes and/or by creating a derived class from the IdentityDbContext class.

    But I wouldn't try changing the primary key structure of the Identity tables. I seriously doubt that will work and if it does, there's no gaurantee that it will work properly.

    Just to get you pointed in the right direction, here's how I modified my IdentityUser class (AspNetUsers table). I added a few properties to store data I retrieve from Active Directory.

    public class ApplicationUser : IdentityUser
    {
        [Required]
        [StringLength(50)]
        public string FirstName { get; set; }
    
        [Required]
        [StringLength(50)]
        public string LastName { get; set; }
    
        [Required]
        public bool Active { get; set; }
    }
    

    Doing this requires that you create a derived class from the IdentityDbContext class that uses the model class above. In mine I rename the Identity tables.

    public class MyDbContext : IdentityDbContext<ApplicationUser>
    {
        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);
    
            //Rename the ASP.NET Identity tables.
            builder.Entity<ApplicationUser>().ToTable("User");
            builder.Entity<IdentityRole>().ToTable("Role");
            builder.Entity<ApplicationRole>().ToTable("Role");
            builder.Entity<IdentityUserRole<string>>().ToTable("UserRole");
            builder.Entity<IdentityUserLogin<string>>().ToTable("UserLogin");
            builder.Entity<IdentityUserClaim<string>>().ToTable("UserClaim");
            builder.Entity<IdentityRoleClaim<string>>().ToTable("UserRoleClaim");
        }
    }
    

    Then in the ConfigureServices() method in Startup.cs

    //Add Identity services.
    services.AddIdentity<ApplicationUser, IdentityRole>()
        .AddEntityFrameworkStores<MyDbContext>()  
        .AddDefaultTokenProviders();