Необработанное исключение после перехода на платформу Entity Framework 4.3.1

Ошибка:

Необработанное исключение: System.Data.SqlClient.SqlException: операция завершилась неудачно, потому что индекс или статистика с именем "IX_ID" уже существуют в таблице "PrivateMakeUpLessons".

Модель (упрощена, построена в отдельном тестовом проекте для отладки):

public abstract class Lesson
{
    public Guid ID { get; set; }
    public string Room { get; set; }
    public TimeSpan Time { get; set; }
    public int Duration { get; set; }
}

public abstract class RecurringLesson : Lesson
{
    public int DayOfWeek { get; set; }
    public DateTime StartDate { get; set; }
    public DateTime EndDate { get; set; }
    public string Frequency { get; set; }
}

public class PrivateLesson : RecurringLesson
{
    public string Student { get; set; }
    public string Teacher { get; set; }
    public virtual ICollection<Cancellation> Cancellations { get; set; }
}

public class Cancellation
{
    public Guid ID { get; set; }
    public DateTime Date { get; set; }
    public virtual PrivateLesson Lesson { get; set; }
    public virtual MakeUpLesson MakeUpLesson { get; set; }
}

public class MakeUpLesson : Lesson
{
    public DateTime Date { get; set; }
    public string Teacher { get; set; }
    public virtual Cancellation Cancellation { get; set; }
}

Конфигурация:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<Lesson>().ToTable("Lessons");
    modelBuilder.Entity<RecurringLesson>().ToTable("RecurringLessons");
    modelBuilder.Entity<PrivateLesson>().ToTable("PrivateLessons");
    modelBuilder.Entity<MakeUpLesson>().ToTable("PrivateMakeUpLessons");

    modelBuilder.Entity<Cancellation>()
        .HasOptional(x => x.MakeUpLesson)
        .WithRequired(x => x.Cancellation);

    base.OnModelCreating(modelBuilder);
}

Примечания:

Это отлично работало в EF 4.2. Что-то не так с моей моделью? Фактическая модель намного сложнее, поэтому у меня все классы абстрагированы. Кроме того, я работаю против существующей базы данных, поэтому мне нужно использовать наследование типа Table-Per-Type.

Если изменить отношение Cancellation на PrivateMakeUpLesson от 1 до 0..1 до 0..1 до 0..1, это сработает. Это нежелательно, потому что вы не можете иметь PrivateMakeUpLesson без Cancellation.

Кроме того, если я делаю PrivateMakeUpLesson НЕ наследованным от Lesson, тогда он также работает, но он является уроком и должен оставаться таким же для существующей бизнес-логики.

Буду признателен за любые рекомендации. Спасибо!

Edit

Запуск щедрости. Я не могу найти документацию о том, что было изменено между EF 4.2 и EF 4.3 в отношении генерации индекса для кода. Ясно, что EF 4.3 создает больше индексов и что схема именования изменилась, но я хочу знать, есть ли ошибка в EF или если что-то принципиально неверно с моей моделью или плавной конфигурацией API.

Ответ 1

Начиная с EF 4.3, индексы добавляются для столбцов freign key во время создания базы данных. Существует ошибка, которая может вызвать создание индекса более одного раза. Это будет исправлено в будущей версии EF.

До тех пор вы можете обойти проблему, создав свою базу данных, используя Migrations вместо инициализаторов базы данных (или метод Database.Create()).

После генерации начальной миграции вам нужно будет удалить избыточный вызов Index().

CreateTable(
    "dbo.PrivateMakeUpLessons",
    c => new
        {
            ID = c.Guid(nullable: false),
            ...
        })
    .PrimaryKey(t => t.ID)
    .ForeignKey("dbo.Lessons", t => t.ID)
    .ForeignKey("dbo.Cancellations", t => t.ID)
    .Index(t => t.ID)
    .Index(t => t.ID); // <-- Remove this

Чтобы продолжить создание базы данных во время выполнения, вы можете использовать инициализатор MigrateDatabaseToLatestVersion.

Ответ 2

По-моему, это явно ошибка.

Проблема начинается с наблюдения, что EF вообще создает индекс IX_ID. Если вы разделите модель на следующую...

public abstract class Lesson
{
    public Guid ID { get; set; }
}

public class RecurringLesson : Lesson
{
}

public class MyContext : DbContext
{
    public DbSet<Lesson> Lessons { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<RecurringLesson>().ToTable("RecurringLessons");
    }
}

... и пусть EF создает схему базы данных, вы получаете две таблицы Lessons и RecurringLessons, как и ожидалось для сопоставления наследования TPT. Но мне интересно, почему он создает два для таблицы RecurringLessons:

  • Индекс PK_RecurringLessons (кластерный, уникальный) с индексом ID
  • Индекс IX_ID (не кластеризованный, а не уникальный) с столбцом индекса ID снова

Я не знаю, есть ли какая-либо польза для базы данных, чтобы иметь второй индекс в том же столбце. Но для моего понимания не имеет смысла 1) создать индекс в том столбце, который уже рассмотрен в кластерном индексе PK, и 2) создать не уникальный index на столбце, который является первичным ключом и поэтому обязательно уникальным.

Кроме того, из-за взаимно однозначного отношения EF пытается создать индекс в таблице зависимого от этой ассоциации, который равен PrivateMakeUpLessons. (Это зависимый (а не главный), потому что Cancellation требуется в объекте MakeUpLesson.)

ID является внешним ключом в этой ассоциации (и первичный ключ в то же время, потому что отношения "один-к-одному" всегда являются совместными ассоциациями первичных ключей в Entity Framework). EF, по-видимому, всегда создает индекс внешнего ключа отношений. Но для отношений "один ко многим" это не проблема, потому что столбец FK отличается от столбца PK. Не так для взаимно-однозначных отношений: FK и PK одинаковы (это ID), поэтому EF пытается создать индекс IX_ID для этого отношения "один-к-одному", который уже существует из-за TPT наследование (что приводит к взаимосвязи "один к одному" с точки зрения базы данных).

Здесь применимо те же соображения, что и выше: Таблица PrivateMakeUpLessons имеет кластерный индекс PK в столбце ID. Почему второй индекс IX_ID в том же столбце требуется вообще?

Кроме того, EF, похоже, не проверяет, что он уже хочет создать индекс с именем IX_ID для наследования TPT, что приведет, наконец, к исключению в базе данных, когда DDL отправляется для создания схемы базы данных.

EF 4.2 (и до) не создавал никаких индексов (кроме индексов PK) вообще, это было введено в EF 4.3, особенно индексы для столбцов FK.

Я не нашел обходного пути. В худшем случае вам необходимо вручную создать схему базы данных и избежать того, что EF пытается ее создать (= отключить инициализацию базы данных). В лучшем случае есть способ отключить автоматическое создание индекса FK, но я не знаю, возможно ли это.

Вы можете отправить отчет об ошибке здесь: http://connect.microsoft.com/VisualStudio

Или, может быть, кто-то из команды разработчиков EF увидит ваш вопрос здесь и предоставит вам решение.

Ответ 3

Я получил очень схожую ошибку с этим в своем коде некоторое время назад. Попробуйте поместить список отмены в класс Lesson. Это решило мою проблему.

Ответ 4

Ниже я описываю 2 сценария, что, вероятно, происходит неправильно. Пожалуйста, прочитайте в глубину, нажав ссылки, которые я предоставил, чтобы узнать больше о моих объяснениях.


Первая
Lesson и RecurringLesson являются abstract классами (поэтому вы хотите иметь его как базовые классы ).
Вы создаете таблицу объектов Lesson и RecurringLesson, которые приведут к структуре Таблица для иерархии. краткое описание
Создание класса базовой таблицы приведет к созданию одной большой таблицы, содержащей столбцы всех унаследованных таблиц. Таким образом, все свойства PrivateLesson, MakeUpLesson и всех других унаследованных объектов будут храниться в таблице Lessons. EF добавит также столбец Discriminator. Значение этого столбца по умолчанию соответствует постоянному имени класса (например, "PrivateLesson" или "MakeUpLesson" ), только столбец, соответствующий этому конкретному объекту (соответствующий значению дискриминатора), будет использоваться в этой конкретной строке.

НО
Вы также сопоставляете унаследованные классы, такие как PrivateLesson и MakeUpLesson. Это заставит EF использовать структуру Таблица для типа, которая приводит к одной таблице для каждого класса. Это может привести к конфликтам, с которыми вы сейчас сталкиваетесь.


Второй
В вашем примере показано, что у вас есть отношения "один-к-одному" (Cancellation -> MakeUpLesson) и отношения "один-ко-многим" (Cancellation -> PrivateLesson), потому что PrivateLesson и MakeUpLesson являются (косвенными), унаследованными от Lesson в комбинации с первым описанным сценарием может вызвать проблемы, так как это приведет к 2 отношениям внешнего ключа в базе данных для каждого объекта. (один использует таблицу для структуры иерархии и одну, используя структуру Table per Type).

Также этот пост может помочь вам определить правильное определение "один-к-одному".


Пожалуйста, проверьте, выполнив следующие действия:
Я предполагаю, что у вас есть собственная тестовая среда, поэтому вы можете создавать новые тестовые базы данных.

1.  Удалите отношения с Cancellation, комментируя все свойства этого класса:

public class PrivateLesson : RecurringLesson
{
    public string Student { get; set; }
    public string Teacher { get; set; }
    //public virtual ICollection<Cancellation> Cancellations { get; set; }
}

public class Cancellation
{
    public Guid ID { get; set; }
    public DateTime Date { get; set; }
    //public virtual PrivateLesson Lesson { get; set; }
    //public virtual MakeUpLesson MakeUpLesson { get; set; }
}

public class MakeUpLesson : Lesson
{
    public DateTime Date { get; set; }
    public string Teacher { get; set; }
    //public virtual Cancellation Cancellation { get; set; }
}

И удалите конфигурацию:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<Lesson>().ToTable("Lessons");
    modelBuilder.Entity<RecurringLesson>().ToTable("RecurringLessons");
    modelBuilder.Entity<PrivateLesson>().ToTable("PrivateLessons");
    modelBuilder.Entity<MakeUpLesson>().ToTable("PrivateMakeUpLessons");

    //modelBuilder.Entity<Cancellation>()
    //    .HasOptional(x => x.MakeUpLesson)
    //    .WithRequired(x => x.Cancellation);

    base.OnModelCreating(modelBuilder);
}

2. Создание новой пустой базы данных
3. Пусть EF генерирует структуру таблицы для вас в этой пустой базе данных.
4. Проверьте первый сценарий. Если это правда, это нужно сначала исправить, используя структуру Таблица для иерархии или Таблица для типа. Вероятно, вы хотите использовать структуру Таблица для иерархии, потому что (если я хорошо понимаю ваш вопрос), уже есть производство Окружающая среда.

Ответ 5

Когда мой проект был обновлен с EF 6.0.2 до EF 6.1.1, у меня возникла такая проблема, а затем вернуться к версии 6.0.2 после возврата старой версии, ошибка исчезла