【问题标题】:adding a foreign key with Code First migration使用 Code First 迁移添加外键
【发布时间】:2012-11-30 17:36:54
【问题描述】:

添加迁移后尝试更新数据库时出现错误。

这是我在添加迁移之前的课程

public class Product
{
    public Product() { }

    public int ProductId { get; set; } 
    public string Name { get; set; }
    public decimal Price { get; set; }
    public bool Istaxable { get; set; }
    public string DefaultImage { get; set; }
    public IList<Feature> Features { get; set; }
    public IList<Descriptor> Descriptors { get; set; }
    public IList<Image> Images { get; set; }
    public IList<Category> Categories { get; set; }
}


public class Feature
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }

}

现在我想在我的 Feature 类中添加一个外键并以这种方式重构这些类:

public class Product
{
    public Product() { }

    public int ProductId { get; set; } 
    public string Name { get; set; }
    public decimal Price { get; set; }
    public bool Istaxable { get; set; }
    public string DefaultImage { get; set; }
    public IList<Feature> Features { get; set; }
    public IList<Descriptor> Descriptors { get; set; }
    public IList<Image> Images { get; set; }
    public IList<Category> Categories { get; set; }
}

public class Feature
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public string Image { get; set; }
    public string VideoLink { get; set; }

    public int ProductId { get; set; }
    public Product Product { get; set; }
}

我使用Add-Migration 命令添加了迁移。 我添加了一个Update-Database 命令,这是我得到的结果:

ALTER TABLE 语句与 FOREIGN KEY 约束冲突 “FK_dbo.ProductFeatures_dbo.Products_ProductId”。发生了冲突 在数据库“CBL”中,表“dbo.Products”,列“ProductId”

我可以做些什么来解决这个问题并让我的迁移恢复正常?

【问题讨论】:

  • Feature 中的 ProductId 值不会出现在 Product 中。

标签: entity-framework foreign-keys entity-framework-migrations


【解决方案1】:

解决此问题的关键是将您的迁移分为两个迁移。首先,添加一个可为空的字段并填写数据。其次,将该字段设为必填外键。

第一次迁移

  1. 将新属性作为可空类型(例如 int?)添加到您的类中

    public class MyOtherEntity
    {
        public int Id { get; set; }
    }
    
    public class MyEntity
    {
        ...
        // New reference to MyOtherEntity
        public int? MyOtherEntityId { get; set; }
        ...
    }
    
  2. 创建迁移。注意:迁移名称并不重要,但“AddBlogPosts1”之类的名称很容易阅读。

    > add-migration AddMyEntityMyOtherEntity1
    
  3. 这应该像这样构建一个迁移:

    public partial class AddMyTableNewProperty1 : DbMigration
    {
        public override void Up()
        {
            AddColumn("dbo.MyEntity", "MyOtherEntityId", c => c.Int());
        }
        public override void Down()
        {
            DropColumn("dbo.MyEntity", "MyOtherEntityId");
        }
    }
    
  4. 现在手动编辑生成的迁移,为新字段添加默认值。最简单的情况是默认值不变。如果需要,您可以在 SQL 中添加更多逻辑。此示例假设所有 MyEntity 实例都指向同一个 ID 为 1 的 MyOtherEntity 实例。

    public partial class AddMyTableNewProperty1 : DbMigration
    {
        public override void Up()
        {
            AddColumn("dbo.MyEntity", "MyOtherEntityId", c => c.Int());
    
            // ADD THIS BY HAND
            Sql(@"UPDATE dbo.MyEntity SET MyOtherEntityId = 1
                  where MyOtherEntity IS NULL");
        }
        public override void Down()
        {
            DropColumn("dbo.MyEntity", "MyOtherEntityId");
        }
    }
    
  5. 更新数据库

    > update-database
    

第二次迁移

  1. 返回您的 MyEntity 类并更改新属性以表示强制外键。

    public class MyEntity
    {
        ...
        // Change the int? to int to make it mandatory
        public int MyOtherEntityId { get; set; }
    
        // Create a reference to the other entity
        public virtual MyOtherEntity MyOtherEntity { get; set; }
        ...
    }
    
  2. 创建另一个迁移

    > add-migration AddMyEntityMyOtherEntity2
    
  3. 这应该会创建如下迁移:

    public partial class AddMyEntityMyOtherEntity2: DbMigration
    {
        public override void Up()
        {
            AlterColumn("dbo.MyEntity", "MyOtherEntityId", c => c.Int(nullable: false));
            CreateIndex("dbo.MyEntity", "MyOtherEntityId");
            AddForeignKey("dbo.MyEntity", "MyOtherEntityId", "dbo.MyOtherEntity", "Id");
        }
        public override void Down()
        {
            DropForeignKey("dbo.MyEntity", "MyOtherEntityId", "dbo.MyOtherEntity");
            DropIndex("dbo.MyEntity", new[] { "MyOtherEntityId" });
            AlterColumn("dbo.MyEntity", "MyOtherEntityId", c => c.Int());
        }
    }
    
  4. 更新数据库

    > update-database
    

其他说明

  1. 此技术适用于在应用程序启动期间应用的迁移。
  2. 可以为 SQL 中的新列添加更复杂的映射,但此处未说明。

【讨论】:

  • 为什么迁移名称不重要?命名就是编程中的一切。
  • 您是否忘记提及必须有一个 id = 1 的 MyOtherEntity 行。否则自定义查询将失败
  • 这次你会得到错误:CREATE UNIQUE INDEX 语句终止,因为对象名称“dbo.MyEntity”和索引名称“IX_MyEntity_MyEntityId”发现重复键。重复键值为 (1)。
【解决方案2】:

这是一个老问题,但目前无需创建单独的迁移,只需几个步骤即可解决此问题:

  1. 使用实体更改运行 Add-Migration(添加了一个新的不可为空的引用属性,在本例中为 ProductId)以构建新的迁移类
  2. 修改新添加的迁移以创建一个可为空的列 (nullable: true) 而不是不可为空的
  3. 在AddColumn下方添加Sql命令根据需要设置列的值
  4. 在下面添加 AlterColumn 命令,这将使列按预期不可为空

在上面的例子中,这看起来像:

    public override void Up()
    {
        AddColumn("dbo.ProductFeatures", "ProductId", c => c.Int(nullable: true));
        Sql("UPDATE [dbo].[ProductFeatures] SET ProductId = (SELECT TOP 1 [Id] FROM [dbo].[Products])");
        AlterColumn("dbo.ProductFeatures", "ProductId", c => c.Int(nullable: false));
        CreateIndex("dbo.ProductFeatures", "ProductId");
        AddForeignKey("dbo.ProductFeatures", "ProductId", "dbo.Products", "Id");
    }

【讨论】:

    【解决方案3】:

    我今天遇到了这个问题。以下是我的处理方式。我确实必须让我的 FK 属性可以为空,这对我来说没什么大不了的。

    我对我的 POCO 进行了更改,生成了迁移,然后将以下内容添加到迁移中。

    public partial class LineItemProductionHistory_v4 : DbMigration
    {
        public override void Up()
        {
            AddColumn("LineItemProductionHistories","TempLineItemId",c=>c.Int());
            Sql("Update LineItemProductionHistories Set TempLineItemId = LineItem_Id");
           .
           . Generated code
           .
            Sql("Update LineItemProductionHistories Set LineItemId = TempLineItemId");
            DropColumn("LineItemProductionHistories", "TempLineItemId");
        }
    }
    

    我只是暂时存储所有外键,让迁移完成添加/删除内容,然后将外键放回新创建的 FK 中。

    【讨论】:

      【解决方案4】:

      当您尝试在已包含数据的表的不可为空的列上添加外键约束时,可能会发生这种情况。如果您的表包含数据,请先尝试删除它们,然后重试更新您的数据库。

      【讨论】:

      • 这错过了迁移的重点(保持数据可靠)。您应该将此作为迁移的一部分进行处理,而不是添加手动删除操作
      • 我不得不在开发过程中进行手动删除,在推出生产之前结构发生了变化。
      【解决方案5】:

      快速的答案是 - 首先为 ProductId 添加一个可为空的列,然后在所有现有行中设置一些默认值,然后将该列设置为非空(如果需要)以供将来插入,最后添加外键约束.

      我为此写了一篇很长的博客文章,其中包含完整的源代码 - http://nodogmablog.bryanhogan.net/2015/04/entity-framework-non-null-foreign-key-migration/

      【讨论】:

        【解决方案6】:

        我不确定这个主题是否仍然是实际的,但至少使用 EF 6,您可以执行以下操作将默认值分配给新的外键。

        这是 EF 默认创建新外键的方式:

                    AddColumn("dbo.Table", "AnotherTableId", c => .Int(nullable: false));
                    CreateIndex("dbo.Table", "AnotherTableId");
                    AddForeignKey("dbo.Table", "AnotherTableId", "dbo.AnotherTable", "Id");
        

        如下修改即可

                    AddColumn("dbo.Table", "AnotherTableId", c => 
                    { 
                        var model = c.Int(nullable: false);
                        model.DefaultValue = 1;
                        return model; 
                    });
                    CreateIndex("dbo.Table", "AnotherTableId");
                    AddForeignKey("dbo.Table", "AnotherTableId", "dbo.AnotherTable", "Id");
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-06-24
          • 2020-01-23
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多