C# 将一对一转换为一对多关系问题

C# 将一对一转换为一对多关系问题,c#,entity-framework,C#,Entity Framework,在不完全清除数据库并重新开始的情况下(我无法合理地对生产数据库执行此操作),将一对一关系更改为一对多关系时,我遇到了一些极端困难。启用自动迁移的实体框架6(也无法更改此设置) 原始数据库架构要求在这些类之间建立一对一的关系(不相关的属性已被删除): 随后,要求更改为一对多(一个作业站点可以有多个作业) 步骤2 public override void Up() { CreateTable( "dbo.JobSites",

在不完全清除数据库并重新开始的情况下(我无法合理地对生产数据库执行此操作),将一对一关系更改为一对多关系时,我遇到了一些极端困难。启用自动迁移的实体框架6(也无法更改此设置)

原始数据库架构要求在这些类之间建立一对一的关系(不相关的属性已被删除):

随后,要求更改为一对多(一个作业站点可以有多个作业)

步骤2

public override void Up()
        {
            CreateTable(
                "dbo.JobSites",
                c => new
                    {
                        JobSiteID = c.Int(nullable: false, identity: true),
                    })
                .PrimaryKey(t => t.JobSiteID);

            AddColumn("dbo.Jobs", "Location_JobSiteID", c => c.Int());
            CreateIndex("dbo.Jobs", "Location_JobSiteID");
            AddForeignKey("dbo.Jobs", "Location_JobSiteID", "dbo.JobSites", "JobSiteID");
        }

        public override void Down()
        {
            DropForeignKey("dbo.Jobs", "Location_JobSiteID", "dbo.JobSites");
            DropIndex("dbo.Jobs", new[] { "Location_JobSiteID" });
            DropColumn("dbo.Jobs", "Location_JobSiteID");
            DropTable("dbo.JobSites");
        }
最初这是可行的。但是,如果我删除本地数据库,并提取生产数据库的新副本,并更新运行步骤1和步骤2迁移的数据库,我会得到一个错误System.InvalidOperationException:“支持“ApplicationDbContext”上下文的模型自数据库创建以来已更改。

所以这很令人沮丧。如果我添加migration Temp,看看它认为模型中发生了什么变化,我会得到与最初完全相同的脚手架,就像它没有注册步骤1和步骤2迁移一样

温度

public override void Up()
        {
            DropIndex("dbo.JobSites", new[] { "JobSiteID" });
            RenameColumn(table: "dbo.Jobs", name: "JobSiteID", newName: "Location_JobSiteID");
            DropPrimaryKey("dbo.JobSites");
            AlterColumn("dbo.JobSites", "JobSiteID", c => c.Int(nullable: false, identity: true));
            AddPrimaryKey("dbo.JobSites", "JobSiteID");
            CreateIndex("dbo.Jobs", "Location_JobSiteID");
            AddForeignKey("dbo.Jobs", "Location_JobSiteID", "dbo.JobSites", "JobSiteID");
        }

        public override void Down()
        {
            DropForeignKey("dbo.Jobs", "Location_JobSiteID", "dbo.JobSites");
            DropIndex("dbo.Jobs", new[] { "Location_JobSiteID" });
            DropPrimaryKey("dbo.JobSites");
            AlterColumn("dbo.JobSites", "JobSiteID", c => c.Int(nullable: false));
            AddPrimaryKey("dbo.JobSites", "JobSiteID");
            RenameColumn(table: "dbo.Jobs", name: "Location_JobSiteID", newName: "JobSiteID");
            CreateIndex("dbo.JobSites", "JobSiteID");
        }
我已经读了所有关于这个的帖子。。一些人建议在运行更新之前清理并重建解决方案,以确保它没有使用过时的构建,并且这不会对结果产生任何影响

我唯一可行的解决方案是更新数据库-targetmigration:0,然后删除现有迁移,并重置所有内容:(

显然,这个故事的寓意是,确保您的关系得到了很好的定义,但我真的希望那里的实体框架大师能够帮助我在不删除生产数据库的情况下解决这个问题

我尝试过的其他方法:使用FluentAPI定义关系。生成相同的迁移代码

我还看到一些帖子建议删除_MigrationHistory表,但这似乎不可行。但看看MigrationHistory表,在step1和step2迁移之后,还有一个额外的自动迁移:

201907271958270_UpdateJobSites_Step1
201907272000385_UpdateJobSites_Step2
201907272038156_AutomaticMigration
在第一步和第二步混合之后,有没有“看到”它运行的内容?-Verbose没有给我任何提示

由于无法使现有列成为标识列而无法工作的存档位

到目前为止一切正常。在这种情况下,我需要使用显式迁移,因此在更新实体对象后运行添加迁移后,生成的迁移如下:

public override void Up()
        {
            DropIndex("dbo.JobSites", new[] { "JobSiteID" });
            RenameColumn(table: "dbo.Jobs", name: "JobSiteID", newName: "Location_JobSiteID");
            DropPrimaryKey("dbo.JobSites");
            AlterColumn("dbo.JobSites", "JobSiteID", c => c.Int(nullable: false, identity: true));
            AddPrimaryKey("dbo.JobSites", "JobSiteID");
            CreateIndex("dbo.Jobs", "Location_JobSiteID");
            AddForeignKey("dbo.Jobs", "Location_JobSiteID", "dbo.JobSites", "JobSiteID");
        }

        public override void Down()
        {
            DropForeignKey("dbo.Jobs", "Location_JobSiteID", "dbo.JobSites");
            DropIndex("dbo.Jobs", new[] { "Location_JobSiteID" });
            DropPrimaryKey("dbo.JobSites");
            AlterColumn("dbo.JobSites", "JobSiteID", c => c.Int(nullable: false));
            AddPrimaryKey("dbo.JobSites", "JobSiteID");
            RenameColumn(table: "dbo.Jobs", name: "Location_JobSiteID", newName: "JobSiteID");
            CreateIndex("dbo.JobSites", "JobSiteID");
        }
运行更新数据库-详细操作失败,错误为

IF EXISTS (SELECT name FROM sys.indexes WHERE name = N'IX_JobSiteID' AND object_id = object_id(N'[dbo].[JobSites]', N'U'))
    DROP INDEX [IX_JobSiteID] ON [dbo].[JobSites]
EXECUTE sp_rename @objname = N'dbo.Jobs.JobSiteID', @newname = N'Location_JobSiteID', @objtype = N'COLUMN'
System.Data.SqlClient.SqlException (0x80131904): Either the parameter @objname is ambiguous or the claimed @objtype (COLUMN) is wrong.
在SQL Server对象资源管理器中检查表时,显示名为JobSiteID的作业表上不存在字段、约束或其他内容。在JobSites表上存在约束:

CONSTRAINT [FK_dbo.JobSites_dbo.Jobs_JobSiteID] FOREIGN KEY ([JobSiteID]) REFERENCES [dbo].[Jobs] ([JobIb])
因此,有几件事正在进行中-看起来支架式迁移认为作业上有一个名为JobSiteID的列,但实际上没有,它正试图将其重命名为Location_JobSiteID。因此..我编辑了到DropForeignKey的迁移(并将其添加到下拉列表中)和Jobs表上Location_JobSiteID的add列(并将其放到下拉列表中):

此迁移是成功的—在我尝试为数据种子或向表中添加新对象之前似乎是如此。我收到一个SqlException:无法将值NULL插入列“JobSiteID”错误。即使在主键重置之前使用identity:true对该列进行更改,它也不会在中拾取identity标志SQL

因此,在将数据库回滚到迁移前的条件之后,我在进行了一些搜索之后再次尝试,添加了“[DatabaseGenerated(DatabaseGeneratedOption.Identity)]”

公共类工地
{
[关键]
[数据库生成(DatabaseGeneratedOption.Identity)]
public int JobSiteID{get;set;}
公共虚拟ICollection作业{get;set;}
}

但是这会产生相同的迁移代码。将FluentAPI与.hasDatabaseGenerateOption(DatabaseGenerateOption.Identity)一起使用也是如此

我终于找到了一个解决方案。请记住,这个项目使用的是自动迁移。基本上Step1和Step2迁移方法是正确的,但是,在Step1和Step2之后应用了第三个自动迁移。我删除了Step2迁移,基本上将其转化为自动迁移。更新数据库时n、 它只应用Step1,然后检查模型,并自动创建Step2的内容,并运行它们,诱使其他开发人员将模型和数据库重新对齐

public partial class ResetJobSites : DbMigration
    {
        public override void Up()
        {
            DropForeignKey("dbo.JobSites", "JobSiteID", "dbo.Jobs");
            DropIndex("dbo.JobSites", new[] { "JobSiteID" });
            DropTable("dbo.JobSites");
        }

        public override void Down()
        {
            CreateTable(
                "dbo.JobSites",
                c => new
                    {
                        JobSiteID = c.Int(nullable: false),
                    })
                .PrimaryKey(t => t.JobSiteID);

            CreateIndex("dbo.JobSites", "JobSiteID");
            AddForeignKey("dbo.JobSites", "JobSiteID", "dbo.Jobs", "JobIb");
        }
    }
总之,要在启用自动迁移时将一对一关系转换为一对多关系,您需要保留一个生产数据库(但愿意接受正在转换的表上的数据丢失)

  • 创建一个显式迁移来解耦这两个表(我通过注释掉类、作业表上的nav属性、DbContext中的DbSet以及构建项目所需的任何其他内容来做到这一点)

  • 进行模型更改,将nav属性添加回作业表,一个ICollection nav属性,将DbSet添加回DbContext,并让自动迁移运行

  • 首先不要让自己处于这种境地


我终于找到了一个解决方案。请记住,这个项目使用的是自动迁移。基本上,Step1和Step2迁移方法是正确的,但是,在Step1和Step2之后应用了第三个自动迁移。我消除了Step2迁移,基本上将其转化为自动迁移。当运行update database时,它将应用程序只需要步骤1,然后检查模型,并自动创建步骤2的内容,并运行它们,诱使其他开发人员将模型和数据库重新对齐

public partial class ResetJobSites : DbMigration
    {
        public override void Up()
        {
            DropForeignKey("dbo.JobSites", "JobSiteID", "dbo.Jobs");
            DropIndex("dbo.JobSites", new[] { "JobSiteID" });
            DropTable("dbo.JobSites");
        }

        public override void Down()
        {
            CreateTable(
                "dbo.JobSites",
                c => new
                    {
                        JobSiteID = c.Int(nullable: false),
                    })
                .PrimaryKey(t => t.JobSiteID);

            CreateIndex("dbo.JobSites", "JobSiteID");
            AddForeignKey("dbo.JobSites", "JobSiteID", "dbo.Jobs", "JobIb");
        }
    }
因此,概括起来就是:
public override void Up()
        {
            DropIndex("dbo.JobSites", new[] { "JobSiteID" });
            RenameColumn(table: "dbo.Jobs", name: "JobSiteID", newName: "Location_JobSiteID");
            DropPrimaryKey("dbo.JobSites");
            AlterColumn("dbo.JobSites", "JobSiteID", c => c.Int(nullable: false, identity: true));
            AddPrimaryKey("dbo.JobSites", "JobSiteID");
            CreateIndex("dbo.Jobs", "Location_JobSiteID");
            AddForeignKey("dbo.Jobs", "Location_JobSiteID", "dbo.JobSites", "JobSiteID");
        }

        public override void Down()
        {
            DropForeignKey("dbo.Jobs", "Location_JobSiteID", "dbo.JobSites");
            DropIndex("dbo.Jobs", new[] { "Location_JobSiteID" });
            DropPrimaryKey("dbo.JobSites");
            AlterColumn("dbo.JobSites", "JobSiteID", c => c.Int(nullable: false));
            AddPrimaryKey("dbo.JobSites", "JobSiteID");
            RenameColumn(table: "dbo.Jobs", name: "Location_JobSiteID", newName: "JobSiteID");
            CreateIndex("dbo.JobSites", "JobSiteID");
        }
IF EXISTS (SELECT name FROM sys.indexes WHERE name = N'IX_JobSiteID' AND object_id = object_id(N'[dbo].[JobSites]', N'U'))
    DROP INDEX [IX_JobSiteID] ON [dbo].[JobSites]
EXECUTE sp_rename @objname = N'dbo.Jobs.JobSiteID', @newname = N'Location_JobSiteID', @objtype = N'COLUMN'
System.Data.SqlClient.SqlException (0x80131904): Either the parameter @objname is ambiguous or the claimed @objtype (COLUMN) is wrong.
CONSTRAINT [FK_dbo.JobSites_dbo.Jobs_JobSiteID] FOREIGN KEY ([JobSiteID]) REFERENCES [dbo].[Jobs] ([JobIb])
public override void Up()
        {
            DropForeignKey("dbo.JobSites", "JobSiteID", "dbo.Jobs");
            DropIndex("dbo.JobSites", new[] { "JobSiteID" });
            AddColumn("dbo.Jobs", "Location_JobSiteID", c => c.Int(nullable: false));
            //RenameColumn(table: "dbo.Jobs", name: "JobSiteID", newName: "Location_JobSiteID");
            DropPrimaryKey("dbo.JobSites");
            AlterColumn("dbo.JobSites", "JobSiteID", c => c.Int(nullable: false, identity: true));
            AddPrimaryKey("dbo.JobSites", "JobSiteID");
            CreateIndex("dbo.Jobs", "Location_JobSiteID");
            AddForeignKey("dbo.Jobs", "Location_JobSiteID", "dbo.JobSites", "JobSiteID");
        }

        public override void Down()
        {
            DropForeignKey("dbo.Jobs", "Location_JobSiteID", "dbo.JobSites");
            DropIndex("dbo.Jobs", new[] { "Location_JobSiteID" });
            DropPrimaryKey("dbo.JobSites");
            AlterColumn("dbo.JobSites", "JobSiteID", c => c.Int(nullable: false));
            AddPrimaryKey("dbo.JobSites", "JobSiteID");
            DropColumn("dbo.Jobs", "Location_JobSiteID");
            //RenameColumn(table: "dbo.Jobs", name: "Location_JobSiteID", newName: "JobSiteID");
            CreateIndex("dbo.JobSites", "JobSiteID");
            AddForeignKey("dbo.JobSites", "JobSiteID", "dbo.Jobs", "JobIb");
        }
public class JobSite
    {
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int JobSiteID { get; set; }

        public virtual ICollection<Job> Jobs { get; set; }
    }
public partial class ResetJobSites : DbMigration
    {
        public override void Up()
        {
            DropForeignKey("dbo.JobSites", "JobSiteID", "dbo.Jobs");
            DropIndex("dbo.JobSites", new[] { "JobSiteID" });
            DropTable("dbo.JobSites");
        }

        public override void Down()
        {
            CreateTable(
                "dbo.JobSites",
                c => new
                    {
                        JobSiteID = c.Int(nullable: false),
                    })
                .PrimaryKey(t => t.JobSiteID);

            CreateIndex("dbo.JobSites", "JobSiteID");
            AddForeignKey("dbo.JobSites", "JobSiteID", "dbo.Jobs", "JobIb");
        }
    }
PM> update-database
Specify the '-Verbose' flag to view the SQL statements being applied to the target database.
Applying explicit migrations: [201907291444500_ResetJobSites].
Applying explicit migration: 201907291444500_ResetJobSites.
Applying automatic migration: 201907291528423_AutomaticMigration.
PM>