【问题标题】:How can I change an int ID column to Guid with EF migration?如何使用 EF 迁移将 int ID 列更改为 Guid?
【发布时间】:2016-07-01 13:53:23
【问题描述】:

我正在使用 EF 代码优先方法,想将 Id 字段更改为 guid,但似乎无法通过以下错误。

这是我的第一次迁移:

public partial class CreateDownloadToken : DbMigration
{
    public override void Up()
    {
        CreateTable(
            "dbo.DownloadTokens",
            c => new
            {
                Id = c.Int(nullable: false, identity: true),
                FileId = c.Int(),
                UserId = c.String(nullable: false, maxLength: 128),
                ValidUntil = c.DateTime(nullable: false),
            })
            .PrimaryKey(t => t.Id)
            .ForeignKey("dbo.Files", t => t.FileId)
            .ForeignKey("dbo.Users", t => t.UserId, cascadeDelete: true)
            .Index(t => t.FileId)
            .Index(t => t.UserId);

    }

    public override void Down()
    {
        DropForeignKey("dbo.DownloadTokens", "UserId", "dbo.Users");
        DropForeignKey("dbo.DownloadTokens", "FileId", "dbo.Files");
        DropIndex("dbo.DownloadTokens", new[] { "UserId" });
        DropIndex("dbo.DownloadTokens", new[] { "FileId" });
        DropTable("dbo.DownloadTokens");
    }
}

后来我意识到我需要 Id 列作为 GUID,所以我更改了我的模型文件:

public class DownloadToken
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    public Guid Id { get; set; }

    public int? FileId { get; set; }

    [ForeignKey("FileId")]
    public virtual File File { get; set; }

    [Required]
    public string UserId { get; set; }

    [ForeignKey("UserId")]
    public virtual User User { get; set; }

    [Required]
    public DateTime ValidUntil { get; set; }
}

当运行Add-Migration ChangeDownloadTokenIdToGuid 时,它会生成这个文件:

public partial class ChangeDownloadTokenIdToGuid : DbMigration
{
    public override void Up()
    {
        DropPrimaryKey("dbo.DownloadTokens");
        AlterColumn("dbo.DownloadTokens", "Id", c => c.Guid(nullable: false));
        AddPrimaryKey("dbo.DownloadTokens", "Id");
    }

    public override void Down()
    {
        DropPrimaryKey("dbo.DownloadTokens");
        AlterColumn("dbo.DownloadTokens", "Id", c => c.Int(nullable: false, identity: true));
        AddPrimaryKey("dbo.DownloadTokens", "Id");
    }
}

使用Update-Database 运行此文件会导致此错误:

Identity column 'Id' must be of data type int, bigint, smallint, tinyint, or decimal or numeric with a scale of 0, and constrained to be nonnullable.

任何想法为什么会发生这种情况?

【问题讨论】:

    标签: c# asp.net entity-framework entity-framework-migrations


    【解决方案1】:

    这是因为无法将之前的Id列的int类型转换为Guid类型(正是尝试执行AlterColumn方法)。此外,错误消息建议您,Id 列的新类型可以是集合中的一种类型:int、bigint、smallint、tinyint 或小数或刻度为 0 的数字,对于它们,可以从int类型进行转换。

    解决方案 - 只需删除 Id 列,然后使用新的 Guid 类型重新创建它,以这种方式更改迁移:

    public partial class ChangeDownloadTokenIdToGuid : DbMigration
    {
        public override void Up()
        {
            DropPrimaryKey("dbo.DownloadTokens");
    
            DropColumn("dbo.DownloadTokens", "Id");
            AddColumn("dbo.DownloadTokens", "Id", c => c.Guid(nullable: false, identity: true));
    
            AddPrimaryKey("dbo.DownloadTokens", "Id");
        }
    
        public override void Down()
        {
            DropPrimaryKey("dbo.DownloadTokens");
    
            DropColumn("dbo.DownloadTokens", "Id");
            AddColumn("dbo.DownloadTokens", "Id", c => c.Int(nullable: false, identity: true));
    
            AddPrimaryKey("dbo.DownloadTokens", "Id");
        }
    }
    

    附:为什么你使用DatabaseGeneratedOption.Computed 属性,而不是 DatabaseGeneratedOption.Identity?

    【讨论】:

      【解决方案2】:

      尽管 Slava Utesinov 有效,但它仅适用于空表或在没有其他表引用您正在转换的表的情况下。所以这个答案将帮助那些最终在这个页面上进行更复杂的数据库设置的人。

      以下是您可以在迁移类中使用的实用程序函数,它应该从 Up/Down 函数中调用。该函数还处理表引用您尝试从 Int 转换为 Guid 的表。此辅助函数假定您要转换的列称为“Id”,但在其他方面应该是相当通用的。

      public void Convert(bool toGuid, string parent, params string[] children)
          {
              if (toGuid)
              {
                  AddColumn($"dbo.{parent}s", "Id2", c => c.Guid(nullable: false, identity: true, defaultValueSql: "newid()"));
              }
              else
              {
                  AddColumn($"dbo.{parent}s", "Id2", c => c.Int(nullable: false, identity: true));
              }
              foreach (var child in children)
              {
                  DropForeignKey($"dbo.{child}s", $"{parent}_Id", $"dbo.{parent}s");
                  DropIndex($"dbo.{child}s", new[] { $"{parent}_Id" });
                  RenameColumn($"dbo.{child}s", $"{parent}_Id", $"old_{parent}_Id");
                  if (toGuid)
                  {
                      AddColumn($"dbo.{child}s", $"{parent}_Id", c => c.Guid());
                  }
                  else
                  {
                      AddColumn($"dbo.{child}s", $"{parent}_Id", c => c.Int());
                  }
                  Sql($"update c set {parent}_Id=p.Id2 from {child}s c inner join {parent}s p on p.Id=c.old_{parent}_Id");
                  DropColumn($"dbo.{child}s", $"old_{parent}_Id");
              }
              DropPrimaryKey($"dbo.{parent}s");
              DropColumn($"dbo.{parent}s", "Id");
              RenameColumn($"dbo.{parent}s", "Id2", "Id");
              AddPrimaryKey($"dbo.{parent}s", "Id");
              foreach (var child in children)
              {
                  CreateIndex($"dbo.{child}s", $"{parent}_Id");
                  AddForeignKey($"dbo.{child}s", $"{parent}_Id", $"dbo.{parent}s", "Id");
              }
          }
      

      因此,在您的情况下,您的 Up/Down 函数将是:

          public override void Up()
          {
              Convert(true,"DownloadToken");
          }
      
          public override void Down()
          {
              Convert(false, "DownloadToken");
          }
      

      【讨论】:

        【解决方案3】:

        我知道这是 EF,但对于像我这样在 2021 年使用 EF Core 遇到此问题的人来说,这是我为改编 Andreas Willadsen 的代码所做的:

        protected override void Up(MigrationBuilder migrationBuilder)
        {
        migrationBuilder.AddColumn<Guid>("Id2", "{Parent}s", nullable: false);
        migrationBuilder.DropForeignKey("FK_{Child}s_{Parent}s_{Parent}Id", "{Child}s");
        migrationBuilder.DropIndex("IX_{Child}s_{Parent}Id", "{Child}s");
        
        //if FK reference exists in child you can wipe the child table or..... instead of drop + add column below, need to call renamecolumn, then addcolumn, then use Andreas' post with migrationBuilder.SQL() and update to convert old keys to guid, then finally drop column.
        migrationBuilder.DropColumn("{Parent}Id", "{Child}s");
        migrationBuilder.AddColumn<Guid>("{Parent}Id", "{Child}s", nullable: false);
        //
        
        migrationBuilder.DropPrimaryKey("PK_{Parent}s", "{Parent}s");
        migrationBuilder.DropColumn("Id", "{Parent}s");
        migrationBuilder.RenameColumn("Id2", "{Parent}s", "Id");
        migrationBuilder.AddPrimaryKey("PK_{Parent}s", "{Parent}s", column: "Id");
        
        migrationBuilder.CreateIndex("IX_{Child}s_{Parent}Id", "{Child}s","{Parent}Id");
        migrationBuilder.AddForeignKey(
            name: "FK_{Child}s_{Parent}s_{Parent}Id",
            table: "{Child}s",
            column: "{Parent}Id",
            principalTable: "{Parent}s",
            principalColumn: "Id",
            onDelete: ReferentialAction.Cascade);
        }
        

        将 {Parent} 替换为您的父表名,将 {Child} 替换为您的子表名,并在 Down() 中调用相同的内容,但将 替换为
        我想知道为什么 ef core 还没有在 add-migration 中实现自动化。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-07-30
          • 2014-04-28
          • 1970-01-01
          • 2020-07-23
          • 2013-07-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多