如何使用 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.
中还没有自动化
我正在使用 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, notDatabaseGeneratedOption.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 姓名,然后调用除了将
我想知道为什么 ef core 在 add-migration.