C# 数据库优先到代码优先EF-迁移键和约束名称与数据库不匹配

C# 数据库优先到代码优先EF-迁移键和约束名称与数据库不匹配,c#,entity-framework,ef-code-first,entity-framework-migrations,ef-database-first,C#,Entity Framework,Ef Code First,Entity Framework Migrations,Ef Database First,我最近使用以下方法将我的项目从“数据库优先”更新为“代码优先”模型: 在我想更新现有表上的FK和PKs之前,一切似乎都正常工作 这是一种1-0,1-1的关系。因此,Company表的PK是DriverScorecardSetting表的FK和PK 因此,这就是工具为DriverScorecardSetting表生成的实体 [Table("DriverScorecardSetting")] public partial class DriverScorecardSetting { [Ke

我最近使用以下方法将我的项目从“数据库优先”更新为“代码优先”模型:

在我想更新现有表上的FK和PKs之前,一切似乎都正常工作

这是一种1-0,1-1的关系。因此,
Company
表的PK是
DriverScorecardSetting
表的FK和PK

因此,这就是工具为
DriverScorecardSetting
表生成的实体

[Table("DriverScorecardSetting")]
public partial class DriverScorecardSetting
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int iCompanyId { get; set; }
    public virtual Company Company { get; set; }
 ....
}
现在我想更新关系并使其成为
1-N
关系。i、 e.1公司许多
驾驶员CoreCardSetting

因此,我添加了一个PK,并将关系转换为1-N

[Table("DriverScorecardSetting")]
public partial class DriverScorecardSetting
{
    [Key]
    public int iDriverScorecardSettingId { get; set; }


    [ForeignKey("Company")]
    public int iCompanyId { get; set; }

    public virtual Company Company { get; set; }
   ...
 }
我还对公司实体进行了更改

问题是当我添加迁移时。密钥的名称与数据库中现有的密钥不同。因此,当我运行迁移时,它在数据库中找不到名称,并且不会删除它们

这就是它创建的迁移

public partial class PKForDriverScorecardSetting : DbMigration
{
    public override void Up()
    {
        DropForeignKey("dbo.DriverScorecardSetting", "iCompanyId", "dbo.Companies");
        DropPrimaryKey("dbo.DriverScorecardSetting");
        AddColumn("dbo.DriverScorecardSetting", "iDriverScorecardSettingId", c => c.Int(nullable: false, identity: true));
        AddPrimaryKey("dbo.DriverScorecardSetting", "iDriverScorecardSettingId");
        AddForeignKey("dbo.DriverScorecardSetting", "iCompanyId", "dbo.Companies", "iCompanyId", cascadeDelete: true);
    }

    public override void Down()
    {
        DropForeignKey("dbo.DriverScorecardSetting", "iCompanyId", "dbo.Companies");
        DropPrimaryKey("dbo.DriverScorecardSetting");
        DropColumn("dbo.DriverScorecardSetting", "iDriverScorecardSettingId");
        AddPrimaryKey("dbo.DriverScorecardSetting", "iCompanyId");
        AddForeignKey("dbo.DriverScorecardSetting", "iCompanyId", "dbo.Companies", "iCompanyId");
    }
}
当我在PackageManager控制台中运行此迁移时,会出现错误,因为EF生成的约束的名称是错误的。这是生成的脚本

IF object_id(N'[dbo].[FK_dbo.DriverScorecardSetting_dbo.Companies_iCompanyId]', N'F') IS NOT NULL
    ALTER TABLE [dbo].[DriverScorecardSetting] DROP CONSTRAINT [FK_dbo.DriverScorecardSetting_dbo.Companies_iCompanyId]
ALTER TABLE [dbo].[DriverScorecardSetting] DROP CONSTRAINT [PK_dbo.DriverScorecardSetting]
ALTER TABLE [dbo].[DriverScorecardSetting] ADD [iDriverScorecardSettingId] [int] NOT NULL IDENTITY
ALTER TABLE [dbo].[DriverScorecardSetting] ADD CONSTRAINT [PK_dbo.DriverScorecardSetting] PRIMARY KEY ([iDriverScorecardSettingId])
ALTER TABLE [dbo].[DriverScorecardSetting] ADD CONSTRAINT [FK_dbo.DriverScorecardSetting_dbo.Companies_iCompanyId] FOREIGN KEY ([iCompanyId]) REFERENCES [dbo].[Companies] ([iCompanyId]) ON DELETE CASCADE
但是约束的初始名称不包括
dbo

现在我知道也许有一种方法可以通过编码FK约定来解决这个问题,但是我该如何重命名约定名称呢?它只是一个内部集合属性

我正在使用EF v6.2。

您可以在创建的迁移中修改Up()和Down()方法。使用使用外键名称的DropForeignKey重载。DropPrimaryKey也需要更改

public partial class PKForDriverScorecardSetting : DbMigration
{
    public override void Up()
    {
        //DropForeignKey("dbo.DriverScorecardSetting", "iCompanyId", "dbo.Companies"); // different name
        DropForeignKey("dbo.DriverScorecardSetting", "FK_DriverScorecardSetting_Companies"); // drop FK by name

        //DropPrimaryKey("dbo.DriverScorecardSetting"); // different name
        DropPrimaryKey("dbo.DriverScorecardSetting", "PK_DriverScorecardSetting"); // drop PK by name

        AddColumn("dbo.DriverScorecardSetting", "iDriverScorecardSettingId", c => c.Int(nullable: false, identity: true));
        AddPrimaryKey("dbo.DriverScorecardSetting", "iDriverScorecardSettingId");
        AddForeignKey("dbo.DriverScorecardSetting", "iCompanyId", "dbo.Companies", "iCompanyId", cascadeDelete: true);
    }

    public override void Down()
    {
        DropForeignKey("dbo.DriverScorecardSetting", "iCompanyId", "dbo.Companies");
        DropPrimaryKey("dbo.DriverScorecardSetting");
        DropColumn("dbo.DriverScorecardSetting", "iDriverScorecardSettingId");

        //AddPrimaryKey("dbo.DriverScorecardSetting", "iCompanyId");// different name
        AddPrimaryKey("dbo.DriverScorecardSetting", "iCompanyId", name:"PK_DriverScorecardSetting");// Add PK with name

        //AddForeignKey("dbo.DriverScorecardSetting", "iCompanyId", "dbo.Companies", "iCompanyId");// different name
        AddForeignKey("dbo.DriverScorecardSetting", "iCompanyId", "dbo.Companies", "iCompanyId", name:"FK_DriverScorecardSetting_Companies");// different name
    }
}
链接:


这是一个已知的工作流问题,在EF6文档的一节中解释:

默认/计算名称可能与现有架构不匹配

迁移在构建迁移时显式指定列和表的名称。但是,在应用迁移时,迁移会计算其他数据库对象的默认名称。这包括索引和外键约束。针对现有架构时,这些计算出的名称可能与数据库中实际存在的名称不匹配

建议的解决方案是手动编辑生成的迁移代码,并使用可选的
name
参数(如另一个答案所述):

如果模型中的未来更改需要更改或删除一个命名不同的数据库对象,则需要修改脚手架迁移以指定正确的名称。迁移API有一个可选的名称参数,允许您执行此操作。例如,您现有的模式可能有一个带有BlogId外键列的Post表,该列的索引名为IndexFk_BlogId。但是,默认情况下,迁移会将此索引命名为IX_BlogId。如果对模型进行更改导致删除此索引,则需要修改scaffold DropIndex调用以指定IndexFk_BlogId名称

当然,没有人愿意手动操作。不幸的是,正如我在对的回答中提到的,PK和FK约束名称的问题是EF6没有元数据项/属性/注释来控制它们。如果有这样一种方法,很可能逆向工程过程会使用它。但是为了百分之百地确定,我检查了源代码,尽管
ForeignKeyOperation
PrimaryKeyOperation
都有一个可设置的属性
Name
,但除了框架式迁移调用之外,其他任何操作都没有指定它

很快,传统观念就消失了。还可以做些什么?虽然我们无法通过元数据控制这一点,但幸运的是,我们可以通过自定义类控制迁移代码的生成:

为基于代码的迁移生成代码的提供程序的基类

由于这是C#,我们将继承、重写该方法,对每个
ForeignKeyOperation
PrimaryKeyOperation
应用我们的命名约定,并让base完成其余的工作。示例实现可以如下所示:

using System;
using System.Collections.Generic;
using System.Data.Entity.Migrations.Design;
using System.Data.Entity.Migrations.Model;
using System.Data.Entity.Migrations.Utilities;
using System.Linq;

class CustomMigrationCodeGenerator : CSharpMigrationCodeGenerator
{
    public override ScaffoldedMigration Generate(string migrationId, IEnumerable<MigrationOperation> operations, string sourceModel, string targetModel, string @namespace, string className)
    {
        foreach (var fkOperation in operations.OfType<ForeignKeyOperation>()
            .Where(op => op.HasDefaultName))
        {
            fkOperation.Name = fkOperation.Name.Replace("dbo.", "");
            // or generate FK name using DependentTable, PrincipalTable and DependentColumns properties,
            // removing schema from table names if needed
        }
        foreach (var pkOperation in operations.OfType<PrimaryKeyOperation>()
            .Concat(operations.OfType<CreateTableOperation>().Select(op => op.PrimaryKey))
            .Where(op => op.HasDefaultName))
        {
            pkOperation.Name = pkOperation.Name.Replace("dbo.", "");
            // or generate PK name using Table and Columns properties,
            // removing schema from table name if needed
        }
        return base.Generate(migrationId, operations, sourceModel, targetModel, @namespace, className);
    }

    protected override void GenerateInline(AddForeignKeyOperation addForeignKeyOperation, IndentedTextWriter writer)
    {
        writer.WriteLine();
        writer.Write(".ForeignKey(" + Quote(addForeignKeyOperation.PrincipalTable) + ", ");
        Generate(addForeignKeyOperation.DependentColumns, writer);
        if (addForeignKeyOperation.CascadeDelete)
            writer.Write(", cascadeDelete: true");
        // { missing in base implementation
        if (!addForeignKeyOperation.HasDefaultName)
        {
            writer.Write(", name: ");
            writer.Write(Quote(addForeignKeyOperation.Name));
        }
        // }
        writer.Write(")");
    }
}

我知道这可以在迁移文件本身中实现,但我正在寻找一种更通用的FK命名设置方法。否则,每次我需要更新数据库中的FK/PK时,我都必须修改迁移文件以匹配数据库。感谢您提供了示例的详细答案。我只是想知道有没有一种方法可以调试这段代码-
CustomMigrationCodeGenerator
?好问题-我也想知道!我通过PMC生成迁移来测试它,查看生成的文件/脚本,删除它,更改代码,然后再次执行,等等。结果表明,调试很简单,只需在需要进入的位置粘贴一个
System.Diagnostics.Debugger.Launch()
。幸亏
internal sealed class Configuration : DbMigrationsConfiguration<MyDbContext>
{
    public Configuration()
    {
        CodeGenerator = new CustomMigrationCodeGenerator();
        // ...
    }
}