C# 实体框架迁移、现有数据非规范化和引入外键

C# 实体框架迁移、现有数据非规范化和引入外键,c#,entity-framework,entity-framework-migrations,C#,Entity Framework,Entity Framework Migrations,我有一个现有的数据库(EF code first),它在一个表(table1)中包含数据,该表表示一个枚举(c),然后是另一个表(table2),该表有一个包含枚举值之一的列 我需要做的是对table1进行反规范化,使其具有table2的外键(枚举值) 就我开始接触的实体而言: public class EnvironmentTypes { [Key] public int EnvironmentTypeId { get; set; } [Required]

我有一个现有的数据库(EF code first),它在一个表(
table1
)中包含数据,该表表示一个枚举(c),然后是另一个表(
table2
),该表有一个包含枚举值之一的列

我需要做的是对
table1
进行反规范化,使其具有
table2
的外键(枚举值)

就我开始接触的实体而言:

public class EnvironmentTypes 
{
    [Key]
    public int EnvironmentTypeId { get; set; }

    [Required]
    [MaxLength(100)]
    public string Name { get; set; }

    [Required]
    [MaxLength(200)]
    public string Description { get; set; }
}
对于
表1

对于
表2

在更改之后,我需要将
表2的实体变成

public class EnvironmentDetails
{
    [Required]
    [Index("IX_AppUserMachine", 1, IsUnique = true)]
    [MaxLength(200)]
    public string ApplicationName { get; set; }

    [Index("IX_AppUserMachine", 2, IsUnique = true)]
    [MaxLength(200)]
    public string MachineName { get; set; }

    [Index("IX_AppUserMachine", 3, IsUnique = true)]
    [MaxLength(50)]
    public string UserName { get; set; }

    [Required]
    [ForeignKey(nameof(EnvironmentTypeId))]
    public virtual EnvironmentTypes EnvironmentType { get; set; }

    public int EnvironmentTypeId { get; set; }
}
创建迁移将提供:

public partial class DenormaliseEnvironmentTypeFromEnvironmentDetails : DbMigration
{
    public override void Up()
    {
        AddColumn("dbo.EnvironmentDetails", "EnvironmentTypeId", c => c.Int(nullable: false);
        CreateIndex("dbo.EnvironmentDetails", "EnvironmentTypeId");
        AddForeignKey("dbo.EnvironmentDetails", "EnvironmentTypeId", "dbo.EnvironmentTypes", "EnvironmentTypeId", cascadeDelete: true);
        DropColumn("dbo.EnvironmentDetails", "EnvironmentType");
    }

    public override void Down()
    {
        AddColumn("dbo.EnvironmentDetails", "EnvironmentType", c => c.Int(nullable: false));
        DropForeignKey("dbo.EnvironmentDetails", "EnvironmentTypeId", "dbo.EnvironmentTypes");
        DropIndex("dbo.EnvironmentDetails", new[] { "EnvironmentTypeId" });
        DropColumn("dbo.EnvironmentDetails", "EnvironmentTypeId");
    }
}
这对于空数据库很好,但是如果我在
table2
中有数据,那么运行迁移会抛出一个与外键约束相关的错误

我的想法是,我应该根据以前的数据填充新的
EnvironmentTypeId
列,因此我尝试修改迁移,将
defaultValueSql
参数添加到
ColumnBuilder

AddColumn("dbo.EnvironmentDetails", "EnvironmentTypeId", c => c.Int(nullable: false, defaultValueSql: "(select [EnvironmentTypeId] from [dbo].[EnvironmentTypes] where [dbo].[EnvironmentTypes].[EnumId] = [EnvironmentType])"));
然而,这会产生错误

Error Number:1046,State:1,Class:15
Subqueries are not allowed in this context. Only scalar expressions are allowed.

是否有任何方法可以在不丢失数据(或完整性)的情况下执行此迁移

您可以使用DbMigration的Sql方法并删除defaultValueSql

Sql("select [EnvironmentTypeId] from [dbo].[EnvironmentTypes] 
     where [dbo].[EnvironmentTypes].[EnumId] = [EnvironmentType]");

在@АааСааПааааа的回答的帮助下,我设法手动修改迁移以维护现有数据。我最终使用的迁移如下所示:

CreateTable("tmp", c => new
{
    DetailsId = c.Int(),
    TypeId = c.Int()
});
Sql("INSERT INTO [dbo].[tmp] select d.Id as DetailsId, t.EnvironmentTypeId as TypeId from [dbo].[EnvironmentDetails] d inner join [dbo].[EnvironmentTypes] t on d.EnvironmentType = t.EnumId");
AddColumn("dbo.EnvironmentDetails", "EnvironmentTypeId", c => c.Int(nullable: false, defaultValue: 1));
CreateIndex("dbo.EnvironmentDetails", "EnvironmentTypeId");
AddForeignKey("dbo.EnvironmentDetails", "EnvironmentTypeId", "dbo.EnvironmentTypes", "EnvironmentTypeId", cascadeDelete: true);
DropColumn("dbo.EnvironmentDetails", "EnvironmentType");
Sql("UPDATE [dbo].[EnvironmentDetails] SET [EnvironmentTypeId] = (SELECT t.[TypeId] from [dbo].[tmp] t where t.DetailsId = Id)");
DropTable("tmp");
因此需要创建一个临时表,以基于当前数据保存表之间的链接

根据需要修改数据模型

然后从临时表填充现有的
表2

终于放下了临时桌子

CreateTable("tmp", c => new
{
    DetailsId = c.Int(),
    TypeId = c.Int()
});
Sql("INSERT INTO [dbo].[tmp] select d.Id as DetailsId, t.EnvironmentTypeId as TypeId from [dbo].[EnvironmentDetails] d inner join [dbo].[EnvironmentTypes] t on d.EnvironmentType = t.EnumId");
AddColumn("dbo.EnvironmentDetails", "EnvironmentTypeId", c => c.Int(nullable: false, defaultValue: 1));
CreateIndex("dbo.EnvironmentDetails", "EnvironmentTypeId");
AddForeignKey("dbo.EnvironmentDetails", "EnvironmentTypeId", "dbo.EnvironmentTypes", "EnvironmentTypeId", cascadeDelete: true);
DropColumn("dbo.EnvironmentDetails", "EnvironmentType");
Sql("UPDATE [dbo].[EnvironmentDetails] SET [EnvironmentTypeId] = (SELECT t.[TypeId] from [dbo].[tmp] t where t.DetailsId = Id)");
DropTable("tmp");