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

时间:2019-01-21 20:11:31

标签: c# entity-framework ef-code-first ef-migrations ef-database-first

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

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

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

Relationship Diagram

这是工具为DriverScorecardSetting表生成的实体。

[Table("DriverScorecardSetting")]
public partial class DriverScorecardSetting
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int iCompanyId { get; set; }
    public virtual Company Company { get; set; }
 ....
}

现在,我想更新该关系并将其设置为1-N关系。即1家公司有许多DriverScorecardSetting

所以我添加了一个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");
    }
}

当我在Package Manager控制台中运行此迁移时,出现错误,因为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

Existing Constraints in DB

现在我知道也许可以通过对FK约定Link进行编码来解决此问题,但是我如何重命名约定名称?它只是一个内部set属性。

readonly name

我正在使用EF v6.2。

2 个答案:

答案 0 :(得分:3)

这是Code First to an Existing Database工作流程的一个已知问题,在EF6文档的Code First Migrations with an existing database - Things to be aware of部分中进行了解释:

  

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

     

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

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

  

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

当然没有人愿意手动执行此操作。不幸的是,正如我在对Unique Indexes convention in EF6的答复中提到的那样,PK和FK约束名称的问题在于EF6没有用于控制它们的元数据项/属性/注释。如果有这种方法,则逆向工程流程很可能会使用它。但是为了百分百地确定,我已经检查了源代码,尽管ForeignKeyOperationPrimaryKeyOperation都有可设置的属性Name,但除脚手架的迁移电话。

很快,约定的想法就死了。还有什么可以做的?好吧,虽然我们无法使用元数据来控制它,但幸运的是,我们可以通过自定义MigrationCodeGenerator类来控制迁移代码的生成

  

提供程序的基类,这些提供程序为基于代码的迁移生成代码。

因为这是C#,所以我们将继承CSharpMigrationCodeGenerator,重写Generate方法,对每个ForeignKeyOperationPrimaryKeyOperation应用命名约定,然后让基数执行其余操作。示例实现可能是这样的:

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

请注意,我们还需要重写(替换)GenerateInline(AddForeignKeyOperation方法的基本实现(该方法在创建FK作为create table操作的一部分时使用),因为当前它有一个错误会忽略Name属性(请参见代码中的注释)。

完成此操作后,您只需通过在DbMigrationsConfiguration派生类构造函数中设置CodeGenerator属性来替换标准的迁移代码生成器:

internal sealed class Configuration : DbMigrationsConfiguration<MyDbContext>
{
    public Configuration()
    {
        CodeGenerator = new CustomMigrationCodeGenerator();
        // ...
    }
}

答案 1 :(得分:1)

您可以在创建的迁移中修改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
    }
}

链接: