2017-03-15 134 views
5

添加一个新的一对一关系到我的表之一后,我无法弄清楚如何为我的数据库中的现有行添加默认数据。在EF Core中添加一对一关系时迁移数据?

我的数据库基本上都是这个样子升级之前:

-- Team -- 
    Name: TEXT 

-- History -- 
    Id: INT 

...其中,历史是有外键指出,它与其他不相关的表。

在我升级我基本上要一个团队,有一个单一的历史,所以我的新的数据库看起来像:

-- Team -- 
    Name: TEXT 
    HistoryId: INT 

-- History -- 
    Id: INT 

我的问题了,但是是我存在于我的数据库团队,他们需要有独特的历史记录行指向,所以我需要为每个现有的团队创建一个新的历史记录行。

我尝试在我的迁移中的Up方法中手动添加条目,但由于我的模型与现有模式不匹配,因此失败。

protected override void Up(MigrationBuilder migrationBuilder) 
{ 
    migrationBuilder.AddColumn<int>(
     name: "HistoryId", 
     table: "Team", 
     nullable: false, 
     defaultValue: 0); 

    using (var db = new XMDBContext()) 
    { 
     foreach (var team in db.Team) 
      team.History = new XMHistory(); 
     db.SaveChanges(); 
    } 

    migrationBuilder.CreateIndex(
     name: "IX_Team_HistoryId", 
     table: "Team", 
     column: "HistoryId", 
     unique: true); 

    migrationBuilder.AddForeignKey(
     name: "FK_Team_History_HistoryId", 
     table: "Team", 
     column: "HistoryId", 
     principalTable: "History", 
     principalColumn: "Id", 
     onDelete: ReferentialAction.Cascade); 
} 

回答

4

不幸的是,目前EF Core不支持从迁移种子数据。该问题在其存储库中被追踪为#629 - Seed Data

我目前看到的唯一解决方案是通过MigrationBuilder.Sql方法使用旧的优秀SQL。不幸的是,没有访问数据库提供者服务,所以下一个适用于SQL Server(尽管我试图只使用标准的SQL命令)。

让原来的模式如下:

public class Team 
{ 
    public int Id { get; set; } 
    public string Name { get; set; } 
} 

public class History 
{ 
    public int Id { get; set; } 
} 

加入FK后从TeamHistory它变成:产生

public class Team 
{ 
    public int Id { get; set; } 
    public string Name { get; set; } 
    public int HistoryId { get; set; } 
    public History History { get; set; } 
} 

自动迁移:

protected override void Up(MigrationBuilder migrationBuilder) 
{ 
    migrationBuilder.AddColumn<int>(
     name: "HistoryId", 
     table: "Team", 
     nullable: false, 
     defaultValue: 0); 

    migrationBuilder.CreateIndex(
     name: "IX_Team_HistoryId", 
     table: "Team", 
     column: "HistoryId"); 

    migrationBuilder.AddForeignKey(
     name: "FK_Team_History_HistoryId", 
     table: "Team", 
     column: "HistoryId", 
     principalTable: "History", 
     principalColumn: "Id", 
     onDelete: ReferentialAction.Cascade); 
} 

现在,之前我们手动插入的CreateIndex命令如下:

migrationBuilder.AddColumn<int>(
    name: "TeamId", 
    table: "History", 
    nullable: true); 

migrationBuilder.Sql(@"insert into History (TeamId) select Id from Team"); 

migrationBuilder.Sql(@"update Team set HistoryId = (select Id from History where TeamId = Team.Id)"); 

migrationBuilder.DropColumn(
    name: "TeamId", 
    table: "History"); 

这个想法很简单。我们建立History表的临时空列TeamId,插入新记录中,Team表中相应TeamId每个记录,然后使用TeamId列从History表作为密钥更新的TeamHistoryId列,最后删除临时专栏。

此时数据转换已完成且可创建FK约束。

远不是良好的做法,但可以用作解决方法。

编辑:Gert Arnold's comments之后,看起来像使用SQL块是正确的路要走。唯一引起我关注的是如何编写数据库不可知和/或特定的SQL。当然,如果一个目标是一个特定的数据库类型,那么这个问题就不存在了。无论如何,如果需要处理不同的数据库类型,总是可以使用标准SQL命令支持的所有目标数据库结合具体if块基于MigrationBuilder.ActiveProvider属性。

+0

看起来不错伊万!我认为这是唯一的出路。即使有Seed方法,它也不会有帮助,因为它在*数据库升级期间不会运行*。此场景总是需要一些中间数据库状态,因为您无法将所需的FK添加到尚未存在的东西。 –

+0

@GertArnold我想你是对的。我正在考虑将迁移分为两部分,比如仅向模型添加可空属性,生成迁移,种子(使用数据填充字段),然后将可空(null)更改为不可空,并将导航属性添加到模型中,以生成第二次迁移。所有这些如果在每次迁移结束时运行'Seed'方法。但是现在我不确定这是否可能,因为在迁移期间实体不会相同。 –

+0

...所以可能是SQL是这样的,只是希望有一些信息/助手(如'MigrationSqlGenerator'中使用'ISqlGenerationHelper')以编写与数据库不相关的SQL。或者直接在'MigrationSqlBuilder'中挂钩的方法。因为我们现在所有的都是'MigrationBuilder.ActiveProvider'字符串(当然比没有更好:)。 –