Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Duplicated foreign keys after restoring missing IdentityUser and IdentityRole navigation properties #9503

Closed
bdominguez opened this issue Aug 21, 2017 · 22 comments

Comments

@bdominguez
Copy link

bdominguez commented Aug 21, 2017

Previously on 1.1.2 IdentityUser and IdentityRole provided the following navigation properties:

User

public virtual ICollection<UserRole> Roles { get; } = new List<UserRole>();
public virtual ICollection<UserClaim> Claims { get; } = new List<UserClaim>();
public virtual ICollection<UserLogin> Logins { get; } = new List<UserLogin>();

Role

public virtual ICollection<UserRole> Users { get; } = new List<UserRole>();
public virtual ICollection<RoleClaim> Claims { get; } = new List<RoleClaim>();

Now if I manually add them (like it's written in 1.x to 2.0 migration docs) I get this when running migrations script:

CREATE TABLE [UserClaims] (
    [Id] int NOT NULL IDENTITY,
    [ClaimType] nvarchar(max) NULL,
    [ClaimValue] nvarchar(max) NULL,
    [UserId] int NOT NULL,
    [UserId1] int NULL,
    CONSTRAINT [PK_UserClaims] PRIMARY KEY ([Id]),
    CONSTRAINT [FK_UserClaims_Users_UserId] FOREIGN KEY ([UserId]) REFERENCES [Users] ([Id]) ON DELETE CASCADE,
    CONSTRAINT [FK_UserClaims_Users_UserId1] FOREIGN KEY ([UserId1]) REFERENCES [Users] ([Id]) ON DELETE NO ACTION
);

CREATE TABLE [UserLogins] (
    [LoginProvider] nvarchar(450) NOT NULL,
    [ProviderKey] nvarchar(450) NOT NULL,
    [ProviderDisplayName] nvarchar(max) NULL,
    [UserId] int NOT NULL,
    [UserId1] int NULL,
    CONSTRAINT [PK_UserLogins] PRIMARY KEY ([LoginProvider], [ProviderKey]),
    CONSTRAINT [FK_UserLogins_Users_UserId] FOREIGN KEY ([UserId]) REFERENCES [Users] ([Id]) ON DELETE CASCADE,
    CONSTRAINT [FK_UserLogins_Users_UserId1] FOREIGN KEY ([UserId1]) REFERENCES [Users] ([Id]) ON DELETE NO ACTION
);

CREATE TABLE [UserRoles] (
    [UserId] int NOT NULL,
    [RoleId] int NOT NULL,
    [RoleId1] int NULL,
    [UserId1] int NULL,
    CONSTRAINT [PK_UserRoles] PRIMARY KEY ([UserId], [RoleId]),
    CONSTRAINT [FK_UserRoles_Roles_RoleId] FOREIGN KEY ([RoleId]) REFERENCES [Roles] ([Id]) ON DELETE CASCADE,
    CONSTRAINT [FK_UserRoles_Roles_RoleId1] FOREIGN KEY ([RoleId1]) REFERENCES [Roles] ([Id]) ON DELETE NO ACTION,
    CONSTRAINT [FK_UserRoles_Users_UserId] FOREIGN KEY ([UserId]) REFERENCES [Users] ([Id]) ON DELETE CASCADE,
    CONSTRAINT [FK_UserRoles_Users_UserId1] FOREIGN KEY ([UserId1]) REFERENCES [Users] ([Id]) ON DELETE NO ACTION
);

Manual workaround:

builder.Entity<Role>()
    .HasMany(e => e.Claims)
    .WithOne()
    .HasForeignKey(e => e.RoleId)
    .IsRequired()
    .OnDelete(DeleteBehavior.Cascade);

builder.Entity<Role>()
    .HasMany(e => e.Users)
    .WithOne()
    .HasForeignKey(e => e.RoleId)
    .IsRequired()
    .OnDelete(DeleteBehavior.Cascade);

builder.Entity<User>()
    .HasMany(e => e.Claims)
    .WithOne()
    .HasForeignKey(e => e.UserId)
    .IsRequired()
    .OnDelete(DeleteBehavior.Cascade);

builder.Entity<User>()
    .HasMany(e => e.Logins)
    .WithOne()
    .HasForeignKey(e => e.UserId)
    .IsRequired()
    .OnDelete(DeleteBehavior.Cascade);

builder.Entity<User>()
    .HasMany(e => e.Roles)
    .WithOne()
    .HasForeignKey(e => e.UserId)
    .IsRequired()
    .OnDelete(DeleteBehavior.Cascade);

Further technical details

EF Core version: 2.0
Database Provider: Microsoft.EntityFrameworkCore.SqlServer
Operating system: Visual Studio 2017

@bdominguez bdominguez changed the title Duplicated foreign keys after readding missing IdentityUser navigation properties Duplicated foreign keys after restoring missing IdentityUser and IdentityRole navigation properties Aug 21, 2017
@ajcvickers
Copy link
Member

@bdominguez Are you saying that when you configure the navigations in the EF model (Labeled as "manual workaround" above) then there are still duplicated FKs? Or only if you add the navigation properties but don't explicitly configure them?

@bdominguez
Copy link
Author

I mean the second case. If I don't put that workaround code (explicit relationships) I get duplicated FKs because it seems that it doesn't resolve relationships correctly.

@ajcvickers
Copy link
Member

This is an issue with the Identity documentation, as discussed in aspnet/Identity#1364. When adding navigations, the existing relationships in the model need to be configured to use the navigations, which requires the code above. If this is not done, then the naigations form a second set of relationships with a new FK.

@adnan-kamili
Copy link

adnan-kamili commented Aug 27, 2017

The above fix removed the extra foreighn keys, but when I added following navigation keys in the UserRole : IdentityUserRole model, it adds duplicate foreign keys in AspNetUserRoles table even after setting the relationship in modelbuilder:

    public class UserRole : IdentityUserRole<string>
    {
        public User User { get; set; }
        public Role Role { get; set; }
    }
     modelBuilder.Entity<UserRole>().HasOne<User>(e => e.User)
                .WithOne().HasForeignKey<UserRole>(e => e.UserId);
            modelBuilder.Entity<UserRole>().HasOne<Role>(e => e.Role)
                .WithOne().HasForeignKey<UserRole>(e => e.RoleId);

@divega
Copy link
Contributor

divega commented Aug 27, 2017

@adnan-kamili the following parts of the original workaround are already configuring the relationships of UserRole:

builder.Entity<Role>()
    .HasMany(e => e.Users)
    .WithOne()
    .HasForeignKey(e => e.RoleId)
    .IsRequired()
    .OnDelete(DeleteBehavior.Cascade);
... 

builder.Entity<User>()
    .HasMany(e => e.Roles)
    .WithOne()
    .HasForeignKey(e => e.UserId)
    .IsRequired()
    .OnDelete(DeleteBehavior.Cascade);

You should be able to map the navigation properties you created by referencing them in the calls to WithOne() in this code.

It seems you are trying to configure the same relationships from the other side, but you are using the wrong cardinality (one-to-one as opposed to one-to-many).

Since you are specifying two conflicting associations on each of the FKs, I assume EF Core let's the last one specified get the FK and resorts to creating an extra FK in shadow state for the original one.

cc @AndriySvyryd

@adnan-kamili
Copy link

adnan-kamili commented Aug 27, 2017

If I comment the code, as you said, It creates two additional foreign keys with names RoleId1 & UserId2

       protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.Entity<Role>()
                .HasMany(e => e.Claims)
                .WithOne()
                .HasForeignKey(e => e.RoleId)
                .IsRequired()
                .OnDelete(DeleteBehavior.Cascade);

            modelBuilder.Entity<User>()
                .HasMany(e => e.Roles)
                .WithOne()
                .HasForeignKey(e => e.UserId)
                .IsRequired()
                .OnDelete(DeleteBehavior.Cascade);

            // modelBuilder.Entity<UserRole>().HasOne<User>(e => e.User)
            //     .WithOne().HasForeignKey<UserRole>(e => e.UserId);
            // modelBuilder.Entity<UserRole>().HasOne<Role>(e => e.Role)
            //     .WithOne().HasForeignKey<UserRole>(e => e.RoleId);

            modelBuilder.Entity<Role>(model =>
            {
                model.HasIndex(r => r.NormalizedName).HasName("RoleNameIndex").IsUnique(false);
                model.HasIndex(r => new { r.NormalizedName, r.TenantId }).HasName("TenantRoleNameIndex").IsUnique();
            });
        }

Additionally, If I don't set the reverse relationship, the include({ "Roles.Role" }) fails with a null exception which otherwise works.

@divega
Copy link
Contributor

divega commented Aug 27, 2017

@adnan-kamili what I suggested was actually not just to comment the code but to reference your new navigations in the WithOne() calls. Did you try that?

The empty WithOnes() are otherwise specifying that those those navigation properties do not represent that relationship, and hence EF Core must assume they represent a separate relationship.

@adnan-kamili
Copy link

Thanks, it worked. So following is the final setting required:

           modelBuilder.Entity<Role>()
                .HasMany(e => e.Claims)
                .WithOne()
                .HasForeignKey(e => e.RoleId)
                .IsRequired()
                .OnDelete(DeleteBehavior.Cascade);

            modelBuilder.Entity<User>()
                .HasMany(e => e.Roles)
                .WithOne("User")
                .HasForeignKey(e => e.UserId)
                .IsRequired()
                .OnDelete(DeleteBehavior.Cascade);

            modelBuilder.Entity<Role>()
                .HasMany(e => e.Users)
                .WithOne("Role")
                .HasForeignKey(e => e.RoleId)
                .IsRequired()
                .OnDelete(DeleteBehavior.Cascade);

@snibe
Copy link

snibe commented Sep 5, 2017

can someone explain specifically where to add these? I am at a loss here sorry I thought I could figure it out but I am stuck.

@ajcvickers
Copy link
Member

@snibe See Add IdentityUser navigation properties here: https://docs.microsoft.com/en-us/aspnet/core/migration/1x-to-2x/identity-2x

@snibe
Copy link

snibe commented Sep 5, 2017

@ajcvickers thanks! Guess I am dense today because i can't figure out where the following lines of code need to go. the rest seems clear to me

Copy
///


/// Navigation property for the roles this user belongs to.
///

public virtual ICollection<IdentityUserRole> Roles { get; } = new List<IdentityUserRole>();

///


/// Navigation property for the claims this user possesses.
///

public virtual ICollection<IdentityUserClaim> Claims { get; } = new List<IdentityUserClaim>();

///


/// Navigation property for this users login accounts.
///

public virtual ICollection<IdentityUserLogin> Logins { get; } = new List<IdentityUserLogin>();

@ajcvickers
Copy link
Member

@snibe In ApplicationUser.

@snibe
Copy link

snibe commented Sep 5, 2017

ok i think i got it.. still stuck on this error though " 'ApplicationRole' does not contain a definition for 'Users' and no extension method 'Users' accepting a first argument of type 'ApplicationRole' could be found"

@snibe
Copy link

snibe commented Sep 6, 2017

sorry i am still at a loss with this stuff. I am now getting this error when trying to run in debug or add migrations.
The entity type 'IdentityUserLogin' requires a primary key to be defined.

is there a better walk through of trying to do this?

I am trying to convert this project >> https://code.msdn.microsoft.com/ASPNET-Core-MVC-Authenticat-ef5942f5

from 1.1 to 2.0 and i have followed >> https://docs.microsoft.com/en-us/aspnet/core/migration/1x-to-2x/identity-2x

with adding the IdentityUser POCO section... I just can't seem to get passed this.

@ranouf
Copy link

ranouf commented Mar 24, 2018

Hi @ajcvickers

I followed your instructions:
in user.cs

public class User : IdentityUser<Guid>{
            [...]
        //I added:
        public virtual ICollection<IdentityUserRole<Guid>> Roles { get; } = new List<IdentityUserRole<Guid>>();
            [...]
}

in apidbcontext.cs:

public class AppDbContext : IdentityDbContext<User, Role, Guid>{
        protected override void OnModelCreating(ModelBuilder builder)
        {
            [...]
            //I added:
            builder.Entity<User>()
                .HasMany(e => e.Roles)
                .WithOne()
                .HasForeignKey(e => e.UserId)
                .IsRequired()
                .OnDelete(DeleteBehavior.Cascade);

            [...]
        }
}

But each time I add-migration I have:

 migrationBuilder.AddColumn<Guid>(
                name: "UserId1",
                table: "AspNetUserRoles",
                nullable: true);

            migrationBuilder.CreateIndex(
                name: "IX_AspNetUserRoles_UserId1",
                table: "AspNetUserRoles",
                column: "UserId1");

            migrationBuilder.AddForeignKey(
                name: "FK_AspNetUserRoles_AspNetUsers_UserId1",
                table: "AspNetUserRoles",
                column: "UserId1",
                principalTable: "AspNetUsers",
                principalColumn: "Id",
                onDelete: ReferentialAction.Restrict);

I m using:

<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.2" />

Maybe your fix doesn t work anymore with the new version of Microsoft.EntityFrameworkCore.Tools?
Or most probably, I missed something in my code :)

How can I add the list of Roles to User?

@ranouf
Copy link

ranouf commented Mar 24, 2018

Hi @adnan-kamili

I followed your instructions:
in ApiDbContext:

            modelBuilder.Entity<User>()
                .HasMany(e => e.Roles)
                .WithOne("User")
                .HasForeignKey(e => e.UserId)
                .IsRequired()
                .OnDelete(DeleteBehavior.Cascade);

But as I said previously I have UserId1 created in AspNetUserRoles.

Can you give more details about what you have done to fix the issue?

Thanks

@ranouf
Copy link

ranouf commented Jun 21, 2018

Hi,

Still not working.

Is there someone who found a fix?

@MaverickMartyn
Copy link

MaverickMartyn commented Jun 26, 2018

Same issue here.
I have been banging my head into the wall for hours now, getting nowhere.
Someone please help me. You will have my eternal grattitude.
After setting up ApplicationRole <-> ApplicationUser mmany-toMany relation as follows:

            builder.Entity<ApplicationRole>()
                .HasMany(e => e.Users)
                .WithOne()
                .HasForeignKey(e => e.RoleId)
                .IsRequired()
                .OnDelete(DeleteBehavior.Cascade);
            builder.Entity<ApplicationUser>()
                .HasMany(e => e.Roles)
                .WithOne()
                .HasForeignKey(e => e.UserId)
                .IsRequired()
                .OnDelete(DeleteBehavior.Cascade);

I keep getting this in my migration:

        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.AddForeignKey(
                name: "FK_AspNetUserRoles_AspNetRoles_RoleId1",
                table: "AspNetUserRoles",
                column: "RoleId",
                principalTable: "AspNetRoles",
                principalColumn: "Id",
                onDelete: ReferentialAction.Cascade);
        }

        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.DropForeignKey(
                name: "FK_AspNetUserRoles_AspNetRoles_RoleId1",
                table: "AspNetUserRoles");
        }

@ajcvickers
Copy link
Member

@MaverickMartyn @ranouf I have written the first draft of a document describing how to customize the Identity model--the PR is here: dotnet/AspNetCore.Docs#7288

I was not able to reproduce the issues you are seeing when following the steps from the document.

@kosstianmailru
Copy link

My the problem was solved after the transfer base.OnModelCreating(modelBuilder); at beginning of method override OnModelCreating
image

@Hatef-Rostamkhani
Copy link

My the problem was solved after the transfer base.OnModelCreating(modelBuilder); at beginning of method override OnModelCreating
image

This is correct like this
https://docs.microsoft.com/en-us/aspnet/core/security/authentication/customize-identity-model?view=aspnetcore-2.2

@pgkdev
Copy link

pgkdev commented Oct 14, 2020

I had the same problem with .net core 3.1, with the creation of duplicated Foreign Keys in the migration.
The solution was this:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, string> { ... }
I was missing this part <ApplicationUser, ApplicationRole, string> where i specify the customized user and role classes.

@ajcvickers ajcvickers closed this as not planned Won't fix, can't repro, duplicate, stale Oct 16, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

10 participants