База данных First to Code First EF - Ключи миграции и имена ограничений не совпадают с БД - PullRequest
0 голосов
/ 21 января 2019

Я недавно обновил свой проект из базы данных сначала в модель кода сначала, используя этот метод: Ссылка

Все работало, пока я не захотел обновить свои FK и PK насуществующая таблица.

Это были отношения 1-0, 1-1.Таким образом, PK таблицы Company был FK и PK таблицы DriverScorecardSetting.

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

Когда я запускаю эту миграцию вКонсоль диспетчера пакетов Я получаю ошибки, потому что имя ограничения, сгенерированного 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 Ссылка , но как мне переименовать название конвенции?Это только внутреннее свойство набора.

readonly name

Я использую EF v6.2.

Ответы [ 2 ]

0 голосов
/ 28 января 2019

Это известная проблема с Code First для существующей базы данных рабочий процесс, объясненный в Code First Migrations с существующей базой данных - Что нужно знать о разделе EF6 документация:

Имена по умолчанию / рассчитанные имена могут не соответствовать существующей схеме

Миграции явно задают имена для столбцов и таблиц, когда он переносит миграции. Однако существуют другие объекты базы данных, для которых Migrations вычисляет имя по умолчанию при применении миграций. Это включает в себя индексы и ограничения внешнего ключа. При нацеливании на существующую схему эти вычисленные имена могут не соответствовать тому, что фактически существует в вашей базе данных.

и предлагаемое решение - вручную отредактировать сгенерированный код миграции и использовать необязательный аргумент name (как указано в другом ответе):

Если для будущих изменений в вашей модели потребуется изменить или отбросить один из объектов базы данных с другим именем, вам нужно будет изменить миграцию в скаффолде, чтобы указать правильное имя. В API миграции есть необязательный параметр Name, который позволяет вам это делать. Например, ваша существующая схема может иметь таблицу Post со столбцом внешнего ключа BlogId с индексом IndexFk_BlogId. Тем не менее, по умолчанию Миграции ожидают, что этот индекс будет называться IX_BlogId. Если вы внесете в свою модель изменения, которые приведут к удалению этого индекса, вам нужно будет изменить вызов DropIndex для скаффолдинга, чтобы указать имя IndexFk_BlogId.

Конечно, никто не хотел бы делать это вручную. К сожалению, как я уже упоминал в своем ответе на соглашение об уникальных индексах в EF6 , проблема с именами ограничений PK и FK заключается в том, что в EF6 нет элемента / свойства / аннотации метаданных для управления ими. Если бы был такой способ, скорее всего, обратный инженер процесс использовал бы его. Но чтобы быть на сто процентов уверенным, я проверил исходный код, и хотя оба параметра ForeignKeyOperation и PrimaryKeyOperation имеют настраиваемое свойство Name, оно не указывается никакой другой операцией, кроме вызовов миграции из скаффолдинга.

Вскоре идея конвенции мертва. Что еще можно сделать? Ну, хотя мы не можем контролировать это с помощью метаданных, к счастью, мы можем контролировать код миграции генерация через пользовательский MigrationCodeGenerator класс:

Базовый класс для провайдеров, которые генерируют код для миграции на основе кода.

Так как это C #, мы унаследуем CSharpMigrationCodeGenerator , переопределим метод Generate , применим наше соглашение об именах к каждому ForeignKeyOperation и PrimaryKeyOperation и пусть база сделает все остальное , Пример реализации может быть таким:

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 создается как часть операции создания таблицы), поскольку в настоящее время в нем есть ошибка, которая игнорирует Name свойство (см. комментарии в коде).

После того, как вы это сделаете, все, что вам нужно, это заменить стандартный генератор кода миграции, установив свойство CodeGenerator внутри вашего DbMigrationsConfiguration конструктора производного класса:

internal sealed class Configuration : DbMigrationsConfiguration<MyDbContext>
{
    public Configuration()
    {
        CodeGenerator = new CustomMigrationCodeGenerator();
        // ...
    }
}
0 голосов
/ 25 января 2019

Вы можете изменить методы 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
    }
}

Ссылки:

...