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

Primary Keys mangled on generic DbSets models after second migration #3545

Closed
TsengSR opened this issue Oct 24, 2015 · 10 comments
Closed

Primary Keys mangled on generic DbSets models after second migration #3545

TsengSR opened this issue Oct 24, 2015 · 10 comments
Assignees
Labels
closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-bug
Milestone

Comments

@TsengSR
Copy link

TsengSR commented Oct 24, 2015

The migration file (i.e. 20151024123838_Init.cs) gets mangled primary keys on second migration.

I am using AspNet Identity in my project and after the initial migration the 2nd (and following) migrations will mangle the primary keys, basically making it impossible to migrate further and having to wipe of the database after each change in the structure.

For example:

public class AuthUser : IdentityUser<Guid>
{
    public AuthUser() { }
    public AuthUser(string userName) : base(userName) { }

    public string FirstName { get; set; }
    public string LastName { get; set; }
}
public class AuthRole : IdentityRole<Guid>
{
    public AuthRole() { }
    public AuthRole(string roleName) : base(roleName) { }
}

public class AuthContext : IdentityDbContext<AuthUser, AuthRole, Guid>
{
}

After setting up the initial project, run a migration

dnx ef migrations add Init

The files genreated are generated correctly. Without further change, add a second migration

dnx ef migrations add Init2

The Init2 file (i.e.20151024123846_Init2.cs) has mangled primary keys:

First imgration (20151024123838_Init.cs):

public partial class Init : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.CreateTable(
            name: "AspNetRoles",
            columns: table => new
            {
                Id = table.Column<Guid>(nullable: false),
                ConcurrencyStamp = table.Column<string>(nullable: true),
                Name = table.Column<string>(nullable: true),
                NormalizedName = table.Column<string>(nullable: true)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_AuthRole", x => x.Id);
            });
        migrationBuilder.CreateTable(
            name: "AspNetUsers",
            columns: table => new
            {
                Id = table.Column<Guid>(nullable: false),
                AccessFailedCount = table.Column<int>(nullable: false),
                ConcurrencyStamp = table.Column<string>(nullable: true),
                DisplayName = table.Column<string>(nullable: true),
                Domain = table.Column<string>(nullable: true),
                DomainUsername = table.Column<string>(nullable: true),
                Email = table.Column<string>(nullable: true),
                EmailConfirmed = table.Column<bool>(nullable: false),
                FirstName = table.Column<string>(nullable: true),
                IsDomainUser = table.Column<bool>(nullable: false),
                LastName = table.Column<string>(nullable: true),
                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_AuthUser", x => x.Id);
            });
        migrationBuilder.CreateTable(
            name: "Application",
            columns: table => new
            {
                ApplicationID = table.Column<string>(nullable: false),
                DisplayName = table.Column<string>(nullable: true),
                LogoutRedirectUri = table.Column<string>(nullable: true),
                RedirectUri = table.Column<string>(nullable: true),
                Secret = table.Column<string>(nullable: true),
                Type = table.Column<int>(nullable: false)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_Application", x => x.ApplicationID);
            });
        migrationBuilder.CreateTable(
            name: "AspNetRoleClaims",
            columns: table => new
            {
                Id = table.Column<int>(nullable: false)
                    .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
                ClaimType = table.Column<string>(nullable: true),
                ClaimValue = table.Column<string>(nullable: true),
                RoleId = table.Column<Guid>(nullable: false)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_IdentityRoleClaim<Guid>", x => x.Id);
                table.ForeignKey(
                    name: "FK_IdentityRoleClaim<Guid>_AuthRole_RoleId",
                    column: x => x.RoleId,
                    principalTable: "AspNetRoles",
                    principalColumn: "Id",
                    onDelete: ReferentialAction.Cascade);
            });
        migrationBuilder.CreateTable(
            name: "AspNetUserClaims",
            columns: table => new
            {
                Id = table.Column<int>(nullable: false)
                    .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
                ClaimType = table.Column<string>(nullable: true),
                ClaimValue = table.Column<string>(nullable: true),
                UserId = table.Column<Guid>(nullable: false)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_IdentityUserClaim<Guid>", x => x.Id);
                table.ForeignKey(
                    name: "FK_IdentityUserClaim<Guid>_AuthUser_UserId",
                    column: x => x.UserId,
                    principalTable: "AspNetUsers",
                    principalColumn: "Id",
                    onDelete: ReferentialAction.Cascade);
            });
        migrationBuilder.CreateTable(
            name: "AspNetUserLogins",
            columns: table => new
            {
                LoginProvider = table.Column<string>(nullable: false),
                ProviderKey = table.Column<string>(nullable: false),
                ProviderDisplayName = table.Column<string>(nullable: true),
                UserId = table.Column<Guid>(nullable: false)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_IdentityUserLogin<Guid>", x => new { x.LoginProvider, x.ProviderKey });
                table.ForeignKey(
                    name: "FK_IdentityUserLogin<Guid>_AuthUser_UserId",
                    column: x => x.UserId,
                    principalTable: "AspNetUsers",
                    principalColumn: "Id",
                    onDelete: ReferentialAction.Cascade);
            });
        migrationBuilder.CreateTable(
            name: "AspNetUserRoles",
            columns: table => new
            {
                UserId = table.Column<Guid>(nullable: false),
                RoleId = table.Column<Guid>(nullable: false)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_IdentityUserRole<Guid>", x => new { x.UserId, x.RoleId });
                table.ForeignKey(
                    name: "FK_IdentityUserRole<Guid>_AuthRole_RoleId",
                    column: x => x.RoleId,
                    principalTable: "AspNetRoles",
                    principalColumn: "Id",
                    onDelete: ReferentialAction.Cascade);
                table.ForeignKey(
                    name: "FK_IdentityUserRole<Guid>_AuthUser_UserId",
                    column: x => x.UserId,
                    principalTable: "AspNetUsers",
                    principalColumn: "Id",
                    onDelete: ReferentialAction.Cascade);
            });
        migrationBuilder.CreateIndex(
            name: "RoleNameIndex",
            table: "AspNetRoles",
            column: "NormalizedName");
        migrationBuilder.CreateIndex(
            name: "EmailIndex",
            table: "AspNetUsers",
            column: "NormalizedEmail");
        migrationBuilder.CreateIndex(
            name: "UserNameIndex",
            table: "AspNetUsers",
            column: "NormalizedUserName");
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropTable("AspNetRoleClaims");
        migrationBuilder.DropTable("AspNetUserClaims");
        migrationBuilder.DropTable("AspNetUserLogins");
        migrationBuilder.DropTable("AspNetUserRoles");
        migrationBuilder.DropTable("Application");
        migrationBuilder.DropTable("AspNetRoles");
        migrationBuilder.DropTable("AspNetUsers");
    }
}

Second, mangeld migration (20151024123846_Init2.cs):

public partial class Init2 : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropForeignKey(name: "FK_Guid>_AuthRole_RoleId", table: "AspNetRoleClaims");
        migrationBuilder.DropForeignKey(name: "FK_Guid>_AuthUser_UserId", table: "AspNetUserClaims");
        migrationBuilder.DropForeignKey(name: "FK_Guid>_AuthUser_UserId", table: "AspNetUserLogins");
        migrationBuilder.DropForeignKey(name: "FK_Guid>_AuthRole_RoleId", table: "AspNetUserRoles");
        migrationBuilder.DropForeignKey(name: "FK_Guid>_AuthUser_UserId", table: "AspNetUserRoles");
        migrationBuilder.DropPrimaryKey(name: "PK_Guid>", table: "AspNetUserRoles");
        migrationBuilder.DropPrimaryKey(name: "PK_Guid>", table: "AspNetUserLogins");
        migrationBuilder.DropPrimaryKey(name: "PK_Guid>", table: "AspNetUserClaims");
        migrationBuilder.DropPrimaryKey(name: "PK_Guid>", table: "AspNetRoleClaims");
        migrationBuilder.AddPrimaryKey(
            name: "PK_IdentityUserRole<Guid>",
            table: "AspNetUserRoles",
            columns: new[] { "UserId", "RoleId" });
        migrationBuilder.AddPrimaryKey(
            name: "PK_IdentityUserLogin<Guid>",
            table: "AspNetUserLogins",
            columns: new[] { "LoginProvider", "ProviderKey" });
        migrationBuilder.AddPrimaryKey(
            name: "PK_IdentityUserClaim<Guid>",
            table: "AspNetUserClaims",
            column: "Id");
        migrationBuilder.AddPrimaryKey(
            name: "PK_IdentityRoleClaim<Guid>",
            table: "AspNetRoleClaims",
            column: "Id");
        migrationBuilder.AddForeignKey(
            name: "FK_IdentityRoleClaim<Guid>_AuthRole_RoleId",
            table: "AspNetRoleClaims",
            column: "RoleId",
            principalTable: "AspNetRoles",
            principalColumn: "Id",
            onDelete: ReferentialAction.Cascade);
        migrationBuilder.AddForeignKey(
            name: "FK_IdentityUserClaim<Guid>_AuthUser_UserId",
            table: "AspNetUserClaims",
            column: "UserId",
            principalTable: "AspNetUsers",
            principalColumn: "Id",
            onDelete: ReferentialAction.Cascade);
        migrationBuilder.AddForeignKey(
            name: "FK_IdentityUserLogin<Guid>_AuthUser_UserId",
            table: "AspNetUserLogins",
            column: "UserId",
            principalTable: "AspNetUsers",
            principalColumn: "Id",
            onDelete: ReferentialAction.Cascade);
        migrationBuilder.AddForeignKey(
            name: "FK_IdentityUserRole<Guid>_AuthRole_RoleId",
            table: "AspNetUserRoles",
            column: "RoleId",
            principalTable: "AspNetRoles",
            principalColumn: "Id",
            onDelete: ReferentialAction.Cascade);
        migrationBuilder.AddForeignKey(
            name: "FK_IdentityUserRole<Guid>_AuthUser_UserId",
            table: "AspNetUserRoles",
            column: "UserId",
            principalTable: "AspNetUsers",
            principalColumn: "Id",
            onDelete: ReferentialAction.Cascade);
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropForeignKey(name: "FK_IdentityRoleClaim<Guid>_AuthRole_RoleId", table: "AspNetRoleClaims");
        migrationBuilder.DropForeignKey(name: "FK_IdentityUserClaim<Guid>_AuthUser_UserId", table: "AspNetUserClaims");
        migrationBuilder.DropForeignKey(name: "FK_IdentityUserLogin<Guid>_AuthUser_UserId", table: "AspNetUserLogins");
        migrationBuilder.DropForeignKey(name: "FK_IdentityUserRole<Guid>_AuthRole_RoleId", table: "AspNetUserRoles");
        migrationBuilder.DropForeignKey(name: "FK_IdentityUserRole<Guid>_AuthUser_UserId", table: "AspNetUserRoles");
        migrationBuilder.DropPrimaryKey(name: "PK_IdentityUserRole<Guid>", table: "AspNetUserRoles");
        migrationBuilder.DropPrimaryKey(name: "PK_IdentityUserLogin<Guid>", table: "AspNetUserLogins");
        migrationBuilder.DropPrimaryKey(name: "PK_IdentityUserClaim<Guid>", table: "AspNetUserClaims");
        migrationBuilder.DropPrimaryKey(name: "PK_IdentityRoleClaim<Guid>", table: "AspNetRoleClaims");
        migrationBuilder.AddPrimaryKey(
            name: "PK_Guid>",
            table: "AspNetUserRoles",
            columns: new[] { "UserId", "RoleId" });
        migrationBuilder.AddPrimaryKey(
            name: "PK_Guid>",
            table: "AspNetUserLogins",
            columns: new[] { "LoginProvider", "ProviderKey" });
        migrationBuilder.AddPrimaryKey(
            name: "PK_Guid>",
            table: "AspNetUserClaims",
            column: "Id");
        migrationBuilder.AddPrimaryKey(
            name: "PK_Guid>",
            table: "AspNetRoleClaims",
            column: "Id");
        migrationBuilder.AddForeignKey(
            name: "FK_Guid>_AuthRole_RoleId",
            table: "AspNetRoleClaims",
            column: "RoleId",
            principalTable: "AspNetRoles",
            principalColumn: "Id",
            onDelete: ReferentialAction.Restrict);
        migrationBuilder.AddForeignKey(
            name: "FK_Guid>_AuthUser_UserId",
            table: "AspNetUserClaims",
            column: "UserId",
            principalTable: "AspNetUsers",
            principalColumn: "Id",
            onDelete: ReferentialAction.Restrict);
        migrationBuilder.AddForeignKey(
            name: "FK_Guid>_AuthUser_UserId",
            table: "AspNetUserLogins",
            column: "UserId",
            principalTable: "AspNetUsers",
            principalColumn: "Id",
            onDelete: ReferentialAction.Restrict);
        migrationBuilder.AddForeignKey(
            name: "FK_Guid>_AuthRole_RoleId",
            table: "AspNetUserRoles",
            column: "RoleId",
            principalTable: "AspNetRoles",
            principalColumn: "Id",
            onDelete: ReferentialAction.Restrict);
        migrationBuilder.AddForeignKey(
            name: "FK_Guid>_AuthUser_UserId",
            table: "AspNetUserRoles",
            column: "UserId",
            principalTable: "AspNetUsers",
            principalColumn: "Id",
            onDelete: ReferentialAction.Restrict);
    }
}

The dnx ef database update will now of course fail.

As you can see "FK_IdentityRoleClaim_AuthRole_RoleId" got mangled into "FK_Guid>_AuthRole_RoleId" and the "IdentityRoleClaim<" was removed.

It's a big game breaker, as migrations stop working when generic models are used.

@bricelam
Copy link
Contributor

Hmm.. I am not able to reproduce this on the nightly builds. @TsengSR, would you mind trying again to see if it's fixed?

@TsengSR
Copy link
Author

TsengSR commented Nov 18, 2015

Yes, it's still there.

Using EntityFramework.MicrosoftSqlServer 7.0.0-rc2-16332 and EntityFramework.Commands 7.0.0-rc2-16332 and Microsoft.AspNet.Identity.EntityFramework 3.0.0-rc2-16069.

It happens only on Models containing generics, even if nothing on the model has been changed. Just calling "dnx ef migrations add Init" twice on a fresh migration and the above models.

I did a few further tests and the issue doesn't appear when using the Standard Identity models (which use string as TKey). In my case above I use Guid.

I used this models in my project

public class AuthUser : IdentityUser<Guid>
{
    public AuthUser() { }
    public AuthUser(string userName) : base(userName) { }

    public string DisplayName { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Domain { get; set; }
    public string DomainUsername { get; set; }
    public bool IsDomainUser { get; set; }
}
public class AuthRole : IdentityRole<Guid>
{
    public AuthRole() { }
    public AuthRole(string roleName) : base(roleName) { }
}

public class AuthContext : IdentityDbContext<AuthUser, AuthRole, Guid>
{
    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        var user = builder.Entity<AuthUser>();
        user.Property(a => a.Domain).HasMaxLength(75);
        //user.Property(a => a.Email).HasMaxLength(150).Required();
    }
}

And in the Startup.cs the following configurations for Identity & EntityFramework:

        // Add EF services to the services container.
        services.AddEntityFramework()
            .AddSqlServer()
            .AddDbContext<AuthContext>(options =>
            {
                options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]);
            });

        services.AddIdentity<AuthUser, AuthRole>(
            options =>
            {
                options.User = new Microsoft.AspNet.Identity.UserOptions
                {
                    RequireUniqueEmail = true
                };
            })
            .AddEntityFrameworkStores<AuthContext, Guid>()
            .AddDefaultTokenProviders();

Removing the Guid everywhere and using .AddEntityFrameworkStores<AuthContext, Guid>() and it will create the migration files correctly.

@bricelam
Copy link
Contributor

Thanks, I'll keep digging. I took ASP.NET Identity out of the picture when attempting to repro, but I'll throw it into the mix to see if it changes anything.

@bricelam
Copy link
Contributor

It looks like the issue is actually generic foreign keys closed over non-built-in types. The following model reproduces the issue. I'll start working on a fix.

class AuthUser
{
    public Guid Id { get; set; }

    [ForeignKey("UserId")]
    public ICollection<IdentityUserRole<Guid>> Roles { get; }
}

class IdentityUserRole<TKey>
{
    public int Id { get; set; }

    public TKey UserId { get; set; }
}

class AuthContext : DbContext
{
    public DbSet<AuthUser> Users { get; set; }
}

@bricelam
Copy link
Contributor

Good bug. This affected table, primary key, foreign key, unique constraint, and index names on a generic entity type.

@bricelam bricelam modified the milestones: 7.0.0, 7.0.0-rc2 Nov 20, 2015
@dnlruzzo
Copy link

This is happening for me too.
What's the best approach to work around this issue?

@TsengSR
Copy link
Author

TsengSR commented Nov 26, 2015

Is the bugfix included in the latest v7.0.0-rc2-16432 nightlies?

@bricelam
Copy link
Contributor

@TsengSR No, I sent out a PR, but we discussed and decided to take a slightly different approach. It should be fixed sometime next week.

@JesperTreetop
Copy link

Is there a workaround available for this in RC1, since RC2 is still some time away?

@bricelam
Copy link
Contributor

bricelam commented Feb 9, 2016

@JesperTreetop Explicitly configuring the entities by calling ToTable might make it work.

@ajcvickers ajcvickers modified the milestones: 1.0.0-rc2, 1.0.0 Oct 15, 2022
@ajcvickers ajcvickers added the closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. label Oct 15, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-bug
Projects
None yet
Development

No branches or pull requests

6 participants