更改生产数据库中的主键数据类型

Changing primary key data type in Production DB

我正在使用 EF 代码优先。我需要更改生产数据库中一个 table 主键的数据类型。

public class Course 
{
   [Key]
   public Guid Id {get; set;}    //This needs to be changed to int

   public string Name {get;set;}

   public virtual ICollection<Group> Groups {get;set;}
}

public class Group
{
   [Key]
   public Guid Id {get; set;}

   public string Name {get;set;}

   public virtual ICollection<Course> Courses{get;set;}
}

由于上面是多对多关系,EF 自动创建了一个连接 table GroupCourses,PK 和 FK 都是 Group_IdCourse_Id。到现在为止还好。

现在我需要将 Course 实体中的主键从 Guid 更改为 int

我将数据类型从 Guid 更改为 int 并成功创建了新迁移。但是,当我尝试 运行 命令 update-database 时,出现以下错误。

Operand type clash: uniqueidentifier is incompatible with int in query

我不知道如何解决上述错误。

另外,我认为在不丢失数据的情况下更改主键字段的数据类型是不可能的(当然,我们可以手动从备份中复制)。希望我是错的。

有人能告诉我有没有更好的方法可以在不丢失数据的情况下实现这一点?

注意:我浏览了其他相关的 SO 帖子和其他互联网论坛,但没有运气。

您收到类型冲突错误,因为生成的迁移尝试直接对受影响的列执行 ALTER COLUMN 以将类型从 Guid 更改为 integer,但这是不可能的因为 Guid 值无法隐式转换为 integer 值。

您必须手动修改迁移。删除 AlterColumn 行。为了在不丢失数据的情况下迁移您现有的课程 ID,您可以暂时重命名现有的 Guid 列(添加后缀 _Old),使用正确的新名称和类型创建新列,填写内容使用 UPDATESELECT 与正确值的关系 table,最后删除旧列:

public override void Up()
{
    DropForeignKey("dbo.GroupCourses", "Course_Id", "dbo.Courses");
    DropIndex("dbo.GroupCourses", new[] { "Course_Id" });
    DropPrimaryKey("dbo.Courses");
    DropPrimaryKey("dbo.GroupCourses");

    //Removed: AlterColumn("dbo.Courses", "Id", c => c.Int(nullable: false, identity: true));
    RenameColumn("dbo.Courses", "Id", "Id_Old"); //Added
    AddColumn("dbo.Courses", "Id", c => c.Int(nullable: false, identity: true)); //Added

    //Removed: AlterColumn("dbo.GroupCourses", "Course_Id", c => c.Int(nullable: false));
    RenameColumn("dbo.GroupCourses", "Course_Id", "Course_Id_Old"); //Added
    AddColumn("dbo.GroupCourses", "Course_Id", c => c.Int(nullable: false)); //Added
    Sql(@"UPDATE gc SET gc.Course_Id = c.Id " 
            + "FROM dbo.GroupCourses as gc " 
            + "INNER JOIN dbo.Courses as c ON gc.Course_Id_Old = c.id_Old"); //Added

    DropColumn("dbo.GroupCourses", "Course_Id_Old"); //Added
    DropColumn("dbo.Courses", "Id_Old");    //Added

    AddPrimaryKey("dbo.Courses", "Id");
    AddPrimaryKey("dbo.GroupCourses", new[] { "Group_Id", "Course_Id" });
    CreateIndex("dbo.GroupCourses", "Course_Id");
    AddForeignKey("dbo.GroupCourses", "Course_Id", "dbo.Courses", "Id", cascadeDelete: true);
}

我在代码中用注释 //Added//Removed 标记了我更改的行。其余代码是在将模型中的 属性 类型更改为 int 后使用 Add-Migration 生成的迁移中的原始代码。

您只需用 UPDATE 填充关系 table 中列中的值。 Courses.Id 列中的值将自动生成,因为它是 identity 列。

您还应该弄清楚 Down() 方法并实施反向过程,或者如果您真的永远不会将数据库降级到以前的迁移,则保持原样。

您也可以通过在迁移和播种器之间拆分代码来做到这一点。这在概念上会更正确,因为您应该使用迁移进行模式修改,使用播种机进行数据修改。但我认为这样更容易,你只需要一次迁移,而使用播种器方法你需要两次迁移:一次包含上面包含的所有代码,除了 UPDATE 和删除 [=18 的句子=] 列,另一个只是删除了 _Old 列。 UPDATE 部分将在播种器中实现。

警告:UPDATE 语句中包含的 SQL 代码适用于 Sql Server,但可能不适用于其他数据库引擎。这是使用迁移而不是播种机的另一个缺点。