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

本文关键字:数据类型 DB | 更新日期: 2023-09-27 18:17:20

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

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自动创建了一个连接表GroupCourses与PK &同时作为Group_IdCourse_Id。直到现在还好。

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

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

操作数类型冲突:查询

中的uniqueidentifier与int不兼容

我不知道如何修复上面的错误。

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

谁能告诉我有没有更好的方法来实现这一点而不丢失数据?

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

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

您会得到类型冲突错误,因为生成的迁移试图直接在受影响的列上执行ALTER COLUMN以将类型从Guid更改为integer,这是不可能的,因为Guid值不能隐式地转换为integer值。

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

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填充关系表中列中的值。Courses.Id列中的值将自动生成,因为它是identity列。

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

您还可以通过在迁移和种子程序之间分割代码来实现这一点。这在概念上更正确,因为应该使用迁移进行模式修改,使用种子器进行数据修改。但我认为这种方式更容易,您只需要一次迁移,而使用种子器方法则需要两次迁移:一次迁移包含上面包括的所有代码,除了UPDATE和删除_Old列的句子,另一次只删除_Old列。UPDATE部分将在种子器中实现。

警告:包含在UPDATE句子中的SQL代码可以与Sql Server一起工作,但可能无法与其他数据库引擎一起工作。这是使用迁移而不是种子器的另一个缺点。