更改生产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_Id
和Course_Id
。直到现在还好。
现在我需要将Course
实体中的主键从Guid
更改为int
。
我将数据类型从Guid
更改为int
,并成功创建了一个新的迁移。但是当我尝试运行命令update-database
时,我得到以下错误。
操作数类型冲突:查询
中的uniqueidentifier与int不兼容
我不知道如何修复上面的错误。
另外,我认为在不丢失数据的情况下改变主键字段的数据类型是不可能的(当然,我们可以手动从备份中复制)。我希望我错了。
谁能告诉我有没有更好的方法来实现这一点而不丢失数据?
注意:我已经浏览了其他相关的SO帖子和其他互联网论坛,但没有运气
您会得到类型冲突错误,因为生成的迁移试图直接在受影响的列上执行ALTER COLUMN
以将类型从Guid
更改为integer
,这是不可能的,因为Guid
值不能隐式地转换为integer
值。
您必须手动修改您的迁移。删除AlterColumn
行。为了迁移现有的课程id而不丢失数据,您可以暂时重命名现有的Guid
列(添加后缀_Old
),使用正确的新名称和类型创建新列,使用UPDATE
和SELECT
用正确的值填充关系表中的内容,最后删除旧列:
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
一起工作,但可能无法与其他数据库引擎一起工作。这是使用迁移而不是种子器的另一个缺点。