如何使用 EF 迁移将 int ID 列更改为 Guid?

How can I change an int ID column to Guid with EF migration?

我正在使用 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.

知道为什么会这样吗?

这是因为无法将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");
    }
}

P.S. Why you use DatabaseGeneratedOption.Computed attribute, not DatabaseGeneratedOption.Identity?

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

下面是一个实用函数,您可以在迁移 class 中使用它,应该从 Up/Down 函数中调用它。该函数还处理 tables 对您尝试从 Int 转换为 Guid 的 table 的引用。此辅助函数假定您正在转换的列称为 '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");
    }

我知道这是 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} 替换为您的 parent table 姓名,将 {Child} 替换为您的 child table 姓名,然后调用除了将 交换外,您的 Down() 中的内容相同。
我想知道为什么 ef core 在 add-migration.

中还没有自动化