EF Core 2.0迁移报错:将Identity ID从string改为int时遇问题
解决ASP.NET Core Identity切换ID类型为int时的迁移报错
我之前在把默认Identity的string ID改成int时,也碰到过一模一样的错误。这个问题的根源是:数据库(比如SQL Server)不允许直接修改现有列的IDENTITY属性——原来的AspNetUsers表的Id列是nvarchar类型且没有自增属性,现在要改成int自增列,必须先删除原列再重建,而EF Core默认的迁移生成逻辑做不到这一点,所以抛出了这个错误。
下面分两种场景给你解决方案:
场景1:新项目,没有生产数据(可以清空数据库)
这是最省心的处理方式:
- 首先删除迁移文件夹里现有的所有文件:
00000000000000_CreateIdentitySchema.cs和20180323155805_Snapshot.cs - 删除本地的测试数据库(可以在SSMS里删除,或者用命令
dotnet ef database drop -f) - 重新创建初始迁移:
# 使用.NET CLI dotnet ef migrations add InitialCreate # 或者Visual Studio的Package Manager Console Add-Migration InitialCreate - 最后更新数据库:
dotnet ef database update # 或者Package Manager Console Update-Database
场景2:已有数据,需要保留现有用户/角色信息
这种情况要手动修改迁移脚本,步骤如下:
- 先强行生成新迁移(即使报错也没关系):
dotnet ef migrations add ChangeIdToInt - 打开生成的
ChangeIdToInt.cs文件,你会看到EF Core尝试用AlterColumn来修改Id列,这就是报错的原因。我们需要替换成手动的表重建逻辑:- 以AspNetUsers表为例,Up方法里的代码要改成:
protected override void Up(MigrationBuilder migrationBuilder) { // 1. 创建临时表,结构和新的AspNetUsers一致(int Id) migrationBuilder.CreateTable( name: "AspNetUsers_Temp", columns: table => new { Id = table.Column<int>(nullable: false) .Annotation("SqlServer:Identity", "1, 1"), UserName = table.Column<string>(maxLength: 256, nullable: true), NormalizedUserName = table.Column<string>(maxLength: 256, nullable: true), Email = table.Column<string>(maxLength: 256, nullable: true), NormalizedEmail = table.Column<string>(maxLength: 256, nullable: true), EmailConfirmed = table.Column<bool>(nullable: false), PasswordHash = table.Column<string>(nullable: true), SecurityStamp = table.Column<string>(nullable: true), ConcurrencyStamp = table.Column<string>(nullable: true), PhoneNumber = table.Column<string>(nullable: true), PhoneNumberConfirmed = table.Column<bool>(nullable: false), TwoFactorEnabled = table.Column<bool>(nullable: false), LockoutEnd = table.Column<DateTimeOffset>(nullable: true), LockoutEnabled = table.Column<bool>(nullable: false), AccessFailedCount = table.Column<int>(nullable: false) }, constraints: table => { table.PrimaryKey("PK_AspNetUsers_Temp", x => x.Id); }); // 2. 把原表的数据导入临时表(用ROW_NUMBER生成自增int ID) migrationBuilder.Sql(@" INSERT INTO AspNetUsers_Temp (Id, UserName, NormalizedUserName, Email, NormalizedEmail, EmailConfirmed, PasswordHash, SecurityStamp, ConcurrencyStamp, PhoneNumber, PhoneNumberConfirmed, TwoFactorEnabled, LockoutEnd, LockoutEnabled, AccessFailedCount) SELECT ROW_NUMBER() OVER (ORDER BY UserName) AS Id, UserName, NormalizedUserName, Email, NormalizedEmail, EmailConfirmed, PasswordHash, SecurityStamp, ConcurrencyStamp, PhoneNumber, PhoneNumberConfirmed, TwoFactorEnabled, LockoutEnd, LockoutEnabled, AccessFailedCount FROM AspNetUsers "); // 3. 删除原表 migrationBuilder.DropTable( name: "AspNetUsers"); // 4. 把临时表重命名为原表名 migrationBuilder.RenameTable( name: "AspNetUsers_Temp", newName: "AspNetUsers"); // 5. 重建原表的索引 migrationBuilder.CreateIndex( name: "EmailIndex", table: "AspNetUsers", column: "NormalizedEmail"); migrationBuilder.CreateIndex( name: "UserNameIndex", table: "AspNetUsers", column: "NormalizedUserName", unique: true, filter: "[NormalizedUserName] IS NOT NULL"); // 对AspNetRoles、AspNetUserRoles等其他Identity关联表,执行同样的操作 } - 注意:关联表(比如AspNetUserRoles)需要同步修改UserId/RoleId类型,并且导入数据时要对应新生成的int ID,保证关联关系正确。
- 以AspNetUsers表为例,Up方法里的代码要改成:
- 修改完迁移脚本后,运行数据库更新:
dotnet ef database update
额外注意事项
- 确保你的
ApplicationDbContext完整指定了所有Identity泛型类型,避免遗漏:public class ApplicationDbContext : IdentityDbContext< ApplicationUser, ApplicationRole, int, IdentityUserClaim<int>, IdentityUserRole<int>, IdentityUserLogin<int>, IdentityRoleClaim<int>, IdentityUserToken<int>> { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } } - 检查项目中所有使用用户ID的地方,比如控制器里的
UserManager<ApplicationUser>,确保没有硬编码string类型的ID变量,全部改成int。 - 如果你用了脚手架生成的Account/Manage页面,也要检查页面和后台代码,把所有string类型的ID引用替换成int。
内容的提问来源于stack exchange,提问作者zolty13
相关产品推荐
相关产品推荐

