You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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:已有数据,需要保留现有用户/角色信息

这种情况要手动修改迁移脚本,步骤如下:

  1. 先强行生成新迁移(即使报错也没关系):
    dotnet ef migrations add ChangeIdToInt
    
  2. 打开生成的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,保证关联关系正确。
  3. 修改完迁移脚本后,运行数据库更新:
    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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 03:58:03