Введение ограничения FOREIGN KEY может вызвать циклы или несколько каскадных путей - почему?

295

Я боролся с этим некоторое время и не могу понять, что происходит. У меня есть сущность Карты, которая содержит Стороны (обычно 2), и у обеих карт и Сторон есть Сцена. Я использую EF Codefirst миграции, и миграции завершаются с ошибкой:

Введение ограничения FOREIGN KEY 'FK_dbo.Sides_dbo.Cards_CardId' в таблицу 'Sides' может привести к возникновению циклов или нескольких каскадных путей. Укажите ON DELETE NO ACTION или ON UPDATE NO ACTION или измените другие ограничения FOREIGN KEY.

Вот моя сущность карты :

public class Card
{
    public Card()
    {
        Sides = new Collection<Side>();
        Stage = Stage.ONE;
    }

    [Key]
    [Required]
    public virtual int CardId { get; set; }

    [Required]
    public virtual Stage Stage { get; set; }

    [Required]
    [ForeignKey("CardId")]
    public virtual ICollection<Side> Sides { get; set; }
}

Вот моя сторона :

public class Side
{
    public Side()
    {
        Stage = Stage.ONE;
    }

    [Key]
    [Required]     
    public virtual int SideId { get; set; } 

    [Required]
    public virtual Stage Stage { get; set; }

    [Required]
    public int CardId { get; set; }

    [ForeignKey("CardId")]
    public virtual Card Card { get; set; }

}

А вот и мой Сценический объект:

public class Stage
{
    // Zero
    public static readonly Stage ONE = new Stage(new TimeSpan(0, 0, 0), "ONE");
    // Ten seconds
    public static readonly Stage TWO = new Stage(new TimeSpan(0, 0, 10), "TWO");

    public static IEnumerable<Stage> Values
    {
        get
        {
            yield return ONE;
            yield return TWO;
        }

    }

    public int StageId { get; set; }
    private readonly TimeSpan span;
    public string Title { get; set; }

    Stage(TimeSpan span, string title)
    {
        this.span = span;
        this.Title = title;
    }

    public TimeSpan Span { get { return span; } }
}

Что странно, если я добавлю следующее в свой класс Stage:

    public int? SideId { get; set; }
    [ForeignKey("SideId")]
    public virtual Side Side { get; set; }

Миграция проходит успешно. Если я открою SSMS и посмотрю на таблицы, я вижу, что Stage_StageIdоно было добавлено Cards(как ожидалось / желательно), однако не Sidesсодержит ссылки на Stage(не ожидается).

Если я тогда добавлю

    [Required]
    [ForeignKey("StageId")]
    public virtual Stage Stage { get; set; }
    public int StageId { get; set; }

В моем классе Side я вижу StageIdстолбец, добавленный в мою Sideтаблицу.

Это работает, но теперь во всем моем приложении любая ссылка на Stageсодержит SideId, что в некоторых случаях совершенно не имеет значения. Я хотел бы просто дать моим Cardи Sideсущностям Stageсвойство, основанное на вышеуказанном классе Stage, не загрязняя класс stage ссылочными свойствами, если это возможно ... что я делаю неправильно?

SB2055
источник
7
Отключите каскадное удаление, допустив пустые значения в ссылках ... поэтому в Sideклассе добавьте целое число, [Required]public int? CardId { get; set; }
равное
2
В EF Core вы должны отключить каскадное удаление с помощью DeleteBehavior.Restrictили DeleteBehavior.SetNull.
Сина Лотфи

Ответы:

371

Поскольку Stageэто необходимо , для всех взаимосвязей «один ко многим», в которых Stageэто происходит, будет включено каскадное удаление по умолчанию. Это означает, что если вы удалите Stageобъект

  • удаление будет каскадно Side
  • удаление будет каскадно напрямую Cardи потому Cardи будет Sideиметь обязательное отношение «один ко многим» с каскадным удалением, включенным по умолчанию снова, затем оно будет каскадно переходить CardкSide

Итак, у вас есть два каскадных пути удаления из Stageв, Sideчто вызывает исключение.

Вы должны либо сделать Stageнеобязательный, по крайней мере, один из объектов (то есть удалить [Required]атрибут из Stageсвойств), либо отключить каскадное удаление с помощью Fluent API (невозможно с аннотациями данных):

modelBuilder.Entity<Card>()
    .HasRequired(c => c.Stage)
    .WithMany()
    .WillCascadeOnDelete(false);

modelBuilder.Entity<Side>()
    .HasRequired(s => s.Stage)
    .WithMany()
    .WillCascadeOnDelete(false);
Slauma
источник
2
Спасибо, Слаума. Если я использую свободный API, как вы продемонстрировали выше, сохранят ли другие поля свое поведение каскадного удаления? Например, мне нужно удалить Стороны при удалении карточек.
SB2055 15.06.13
1
@ SB2055: Да, это повлияет только на отношения с Stage. Другие отношения остаются неизменными.
Слаума
2
Есть ли способ узнать, какие свойства вызывают ошибку? У меня та же проблема, и, глядя на мои занятия, я не вижу, где находится цикл
Родриго Хуарес,
4
Это ограничение в их реализации? Мне кажется, что Stageудаление может каскадно Sideпроходить как напрямую, так и черезCard
aaaaaa
1
Предположим, мы установили CascadeOnDelete в false. Затем мы удалили запись этапа, которая связана с одной из записей карты. Что происходит с Card.Stage (FK)? Это остается тем же самым? или для него установлено значение Null?
Нинбит
61

У меня был стол, который имел круговые отношения с другими, и я получал ту же ошибку. Оказывается, речь идет о внешнем ключе, который не обнулялся. Если ключ не имеет значения NULL, связанный объект должен быть удален, а циклические отношения не допускают этого. Так что используйте обнуляемый внешний ключ.

[ForeignKey("StageId")]
public virtual Stage Stage { get; set; }
public int? StageId { get; set; }
Джем Мутлу
источник
5
Я удалил тег [Требуется], но другой важной вещью было использовать int?вместо того, intчтобы позволить ему быть обнуляемым.
VSB
1
Я пробовал много разных способов отключить каскадное удаление и ничего не получалось - это исправлено!
ambog36
5
Вы не должны этого делать, если не хотите, чтобы для Stage было задано значение NULL (Stage был обязательным полем в исходном вопросе).
cfwall
35

Любой, кто интересуется, как это сделать в ядре EF:

      protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                foreach (var relationship in modelBuilder.Model.GetEntityTypes().SelectMany(e => e.GetForeignKeys()))
                {
                    relationship.DeleteBehavior = DeleteBehavior.Restrict;
                }
           ..... rest of the code.....
Nexus23
источник
3
Это отключило бы каскадное удаление на всех отношениях. Каскадное удаление может быть желательной функцией для некоторых случаев использования.
Blaze
15
В качестве альтернативыbuilder.HasOne(x => x.Stage).WithMany().HasForeignKey(x => x.StageId).OnDelete(DeleteBehavior.Restrict);
печенье
@Biscuits Либо методы расширения менялись с течением времени, либо вы забыли, builder _ .Entity<TEntity>() _прежде чем HasOne() можно вызвать ...
ViRuSTriNiTy
1
@ViRuSTriNiTy, моему фрагменту 2 года. Но я думаю, что вы правы - в настоящее время это будет сделано, когда вы решите реализовать IEntityTypeConfiguration<T>. Я не помню, чтобы видел builder.Entity<T>метод в те дни, но я могу ошибаться. Тем не менее, они оба будут работать :)
Печенье
21

Я получал эту ошибку для многих объектов, когда переходил с модели EF7 на версию EF6. Я не хотел проходить каждую сущность по одному, поэтому я использовал:

builder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
builder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
Шон
источник
2
Это должно быть добавлено в класс (ы), которые наследуются от DbContext, например, в методе OnModelCreating. Конструктор типа DbModelBuilder
CodingYourLife
Это сработало для меня; .NET 4.7, EF 6. Один камень преткновения был в том, что я получил ошибку, поэтому, когда я перегенерировал сценарий миграции с удаленными этими соглашениями, он, казалось, не помог. Запуск «Add-Migration» с «-Force» очистил все и перестроил его, включая приведенные выше соглашения. Проблема решена ...
Джеймс Джойс
Они не существуют в ядре .net, есть ли там аналог?
Jjxtra
20

Вы можете установить для cascadeDelete значение false или true (в вашем методе Up () миграции). Зависит от вашего требования.

AddForeignKey("dbo.Stories", "StatusId", "dbo.Status", "StatusID", cascadeDelete: false);
Мусакхир Сайед
источник
2
@Mussakkhir спасибо за ответ. Ваш путь очень элегантный и более законченный - он более точен и направлен непосредственно на проблему, с которой я столкнулся!
Нозим Туракулов
Только не забывайте, что UPметод может быть изменен внешними операциями.
Дементик
8

В .NET Core я изменил параметр onDelete на ReferencialAction.NoAction

         constraints: table =>
            {
                table.PrimaryKey("PK_Schedule", x => x.Id);
                table.ForeignKey(
                    name: "FK_Schedule_Teams_HomeId",
                    column: x => x.HomeId,
                    principalTable: "Teams",
                    principalColumn: "Id",
                    onDelete: ReferentialAction.NoAction);
                table.ForeignKey(
                    name: "FK_Schedule_Teams_VisitorId",
                    column: x => x.VisitorId,
                    principalTable: "Teams",
                    principalColumn: "Id",
                    onDelete: ReferentialAction.NoAction);
            });
Майк Джонс
источник
7

У меня была эта проблема также, я решил это мгновенно с этим ответом из аналогичной темы

В моем случае я не хотел удалять зависимую запись при удалении ключа. Если это так, то просто измените логическое значение в миграции на false:

AddForeignKey("dbo.Stories", "StatusId", "dbo.Status", "StatusID", cascadeDelete: false);

Скорее всего, если вы создаете отношения, которые выдают эту ошибку компилятора, но НЕ хотите поддерживать каскадное удаление; у вас есть проблема с вашими отношениями.

jonc.js
источник
6

Я исправил это. Когда вы добавляете миграцию, в методе Up () будет такая строка:

.ForeignKey("dbo.Members", t => t.MemberId, cascadeDelete:True)

Если вы просто удалите cascadeDelete с конца, он будет работать.

Усман Хан
источник
5

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

Добавьте этот метод в класс контекстной базы данных:

protected override void OnModelCreating(DbModelBuilder modelBuilder) {
    modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
}
sgrysoft
источник
1

Это звучит странно, и я не знаю почему, но в моем случае это происходило потому, что моя ConnectionString использовала "." в атрибуте «источник данных». Как только я изменил его на "localhost", он заработал как шарм. Никаких других изменений не было.

Марко Алвес
источник
1

В .NET Core я играл со всеми верхними ответами - но безуспешно. Я внес много изменений в структуру БД и каждый раз добавлял новые попытки миграции update-database, но получал одну и ту же ошибку.

Затем я начал remove-migrationодин за другим, пока консоль диспетчера пакетов не выдавала мне исключение:

Миграция '20170827183131 _ ***' уже применена к базе данных

После этого я добавил новую миграцию ( add-migration) и update-database успешно

Таким образом, мое предложение было бы: очистить все ваши временные миграции, до вашего текущего состояния БД.

rock_walker
источник
1

Существующие ответы великолепны. Я просто хотел добавить, что столкнулся с этой ошибкой по другой причине. Я хотел создать начальную миграцию EF для существующей БД, но я не использовал флаг -IgnoreChanges и применил команду Update-Database для пустой базы данных (также для существующих сбоев).

Вместо этого мне пришлось выполнить эту команду, когда текущая структура БД является текущей:

Add-Migration Initial -IgnoreChanges

Вероятно, существует реальная проблема в структуре БД, но спасите мир по одному шагу за раз ...

CodingYourLife
источник
1

Простой способ, редактировать файл миграция (cascadeDelete: true)в (cascadeDelete: false)то после назначения команды Update-Database в вашем Package Manager Console.if это проблема с вашей последней миграцией , то все в порядке. В противном случае проверьте свою предыдущую историю миграции, скопируйте эти вещи, вставьте в последний файл миграции, после чего сделайте то же самое. это прекрасно работает для меня.

Нирошан Кумарасамы
источник
1
public partial class recommended_books : DbMigration
{
    public override void Up()
    {
        CreateTable(
            "dbo.RecommendedBook",
            c => new
                {
                    RecommendedBookID = c.Int(nullable: false, identity: true),
                    CourseID = c.Int(nullable: false),
                    DepartmentID = c.Int(nullable: false),
                    Title = c.String(),
                    Author = c.String(),
                    PublicationDate = c.DateTime(nullable: false),
                })
            .PrimaryKey(t => t.RecommendedBookID)
            .ForeignKey("dbo.Course", t => t.CourseID, cascadeDelete: false) // was true on migration
            .ForeignKey("dbo.Department", t => t.DepartmentID, cascadeDelete: false) // was true on migration
            .Index(t => t.CourseID)
            .Index(t => t.DepartmentID);

    }

    public override void Down()
    {
        DropForeignKey("dbo.RecommendedBook", "DepartmentID", "dbo.Department");
        DropForeignKey("dbo.RecommendedBook", "CourseID", "dbo.Course");
        DropIndex("dbo.RecommendedBook", new[] { "DepartmentID" });
        DropIndex("dbo.RecommendedBook", new[] { "CourseID" });
        DropTable("dbo.RecommendedBook");
    }
}

Если миграция завершается неудачно, вам предоставляется несколько вариантов: «Введение ограничения FOREIGN KEY» FK_dbo.RecommendedBook_dbo.Department_DepartmentID »в таблице« RecommendedBook »может вызывать циклы или несколько каскадных путей. Укажите ON DELETE NO ACTION или ON UPDATE NO ACTION или измените другие ограничения FOREIGN KEY. Не удалось создать ограничение или индекс. Смотрите предыдущие ошибки.

Вот пример использования «изменить другие ограничения FOREIGN KEY», установив для «cascadeDelete» значение false в файле миграции, а затем запустив «update-database».

Кристофер Говендер
источник
0

Ни одно из вышеупомянутых решений не помогло мне. То, что мне нужно было сделать, это использовать обнуляемый int (int?) Для внешнего ключа, который не требовался (или не является ненулевым ключом столбца), а затем удалить некоторые из моих миграций.

Начните с удаления миграций, затем попробуйте обнуляемый int.

Проблемой была как модификация, так и дизайн модели. Нет необходимости в изменении кода.

Айсон Бакстер
источник
-1

Сделайте ваши атрибуты внешнего ключа обнуляемыми. Это будет работать.

Умайр Джавед
источник
1
что ответ в комментариях под вопросы уточняйте пожалуйста
Костя Мололкин