Перенос Entity framework, денормализация существующих данных и внедрение внешнего ключа

#c# #entity-framework #entity-framework-migrations

#c# #сущность-фреймворк #entity-framework-миграции

Вопрос:

У меня есть существующая база данных (сначала код EF), в которой есть данные в таблице ( 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; }
}
  

для table1

и

 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]
    public EnvironmentType EnvironmentType { get; set; }
}
  

для table2

и после изменений, которые мне нужны, чтобы создать объект для table2 , становится

 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.
  

Есть ли какой-либо способ выполнить эту миграцию без потери данных (или целостности)

Ответ №1:

С помощью ответа @Александр-Пашкин мне удалось вручную изменить миграцию для сохранения существующих данных. Миграция, которую я, наконец, использовал, выглядела так:

 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");
  

So включает в себя создание временной таблицы для хранения связи между таблицами на основе текущих данных

внесение необходимых изменений в модель данных

затем заполнение существующей table2 из временной таблицы

наконец, удаление временной таблицы.

Ответ №2:

Вы можете сделать это, используя Sql-метод миграции DbMigration и удалив defaultValueSql

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