Введення обмеження "ІНОЗЕМНИЙ КЛЮЧ" може спричинити цикли або кілька каскадних шляхів - чому?


295

Я певний час боровся з цим і не можу повністю зрозуміти, що відбувається. У мене є об'єкт картки, який містить сторони (зазвичай 2) - і картки, і сторони мають етап. Я використовую EF Codefirst міграції, і міграції не вдається з цією помилкою:

Введення обмеження FOREIGN KEY 'FK_dbo.Sides_dbo.Cards_CardId' на таблиці 'Sides' може спричинити цикли або кілька каскадних шляхів. Вкажіть УВІДКЛЮЧАТИ НЕ ДІЙ чи НА ОНОВЛЕННЯ НЕ ДІЇ, або змініть інші обмеження ЗОВНІШНЬОГО КЛЮЧА.

Ось моя картка особа:

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

Для мого бічного класу я бачу StageIdстовпець, доданий до моєї Sideтаблиці.

Це працює, але зараз у всій моїй заяві будь-яке посилання на Stageмістить а SideId, що в деяких випадках абсолютно не має значення. Я хотів би просто надати моєму Cardта Sideсукупності Stageвластивості на основі вищевказаного класу Stage, не забруднюючи клас класу еталонними властивостями, якщо це можливо ... що я роблю неправильно?


7
Вимкнути каскадне видалення, дозволяючи нульовим значенням у посиланнях ... тому в SideClass додайте Nullable ціле число та видаліть [Required]атрибут =>public int? CardId { get; set; }
Jaider

2
У EF Core слід відключити видалення каскаду за допомогою DeleteBehavior.Restrictабо DeleteBehavior.SetNull.
Сина Лотфі

Відповіді:


371

Оскільки Stageце потрібно , всі стосунки "один до багатьох", в яких Stageбере участь, матимуть каскадне видалення за умовчанням. Це означає, що якщо ви видалите Stageсутність

  • видалення буде каскадно безпосередньо до Side
  • видалення буде каскадно безпосередньо до Cardі тому, що Cardі Sideматиме необхідний зв'язок "один на багато" з каскадним видаленням, включеним за замовчуванням знову, тоді він буде каскадом з CardдоSide

Отже, у вас є два каскадні шляхи видалення від - Stageдо, Sideщо викликає виняток.

Потрібно або зробити Stageнеобов'язковим принаймні один із об'єктів (тобто видалити [Required]атрибут із Stageвластивостей) або відключити каскадне видалення за допомогою API Fluent (неможливо з анотаціями даних):

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

modelBuilder.Entity<Side>()
    .HasRequired(s => s.Stage)
    .WithMany()
    .WillCascadeOnDelete(false);

2
Спасибі Слаума. Якщо я використовую вільний API, як ви продемонстрували вище, чи будуть інші поля зберігати свою поведінку каскаду? Наприклад, мені ще потрібно видалити бічні сторони, коли видаляються картки.
SB2055

1
@ SB2055: Так, це вплине лише на стосунки з боку Stage. Інші відносини залишаються незмінними.
Слаума

2
Чи є спосіб дізнатися, які властивості викликають помилку? У мене така ж проблема, і дивлячись на свої заняття, я не бачу, де цей цикл
Родріго Хуарес

4
Це обмеження в їх реалізації? Мені здається прекрасним Stageвидалення, щоб спуститися Sideяк безпосередньо, так і черезCard
aaaaaa

1
Припустимо, ми встановимо CascadeOnDelete на значення false. Потім ми видалили сценічний запис, який пов’язаний з одним із записів Карт. Що відбувається з Card.Stage (FK)? Чи залишається він таким же? або встановлено Null?
нінбіт

61

У мене була таблиця, яка мала кругові стосунки з іншими, і я отримував ту ж помилку. Виявляється, мова йде про зовнішній ключ, який не був нульовим. Якщо ключ не є нульовим, пов'язаний об'єкт повинен бути видалений, а кругові відносини цього не дозволяють. Тому використовуйте нульовий зовнішній ключ.

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

5
Я видалив [Обов’язковий] тег, але важливою річчю було використання int?замість того, intщоб він міг бути зведеним нанівець.
VSB

1
Я спробував багато різних способів вимкнення каскаду видалення, і нічого не вийшло - це виправлено!
ambog36

5
Не слід робити цього, якщо ви не хочете дозволити встановленню 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.....

3
Це призведе до вимкнення каскадного видалення всіх відносин. Каскадне видалення може бути бажаною особливістю для деяких випадків використання.
Блейз

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. Одним каменем спотикання було те, що я отримав помилку, тому коли я регенерувався за допомогою сценарію міграції з видаленими цими умовами, він не з’явився на допомогу. Запуск "Додавання міграції" за допомогою "-Force" очистив усе це та відновив його, включаючи ці вищезгадані конвенції. Проблема вирішена ...
Джеймс Джойс

Їх немає в ядрі .net, будь-якого еквівалента там?
jjxtra

@jjxtra перевірити stackoverflow.com/questions/46526230 / ...
Sean

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

У мене було і це питання, я вирішив його миттєво цією відповіддю з подібної теми

У моєму випадку я не хотів видаляти залежний запис про видалення ключа. Якщо це так у вашій ситуації, просто змініть булеве значення міграції на помилкове:

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

Швидше за все, якщо ви створюєте відносини, які кидають цю помилку компілятора, але НЕ хочуть підтримувати каскадне видалення; у вас є проблеми зі своїми стосунками.


6

Я це зафіксував. Коли ви додаєте міграцію, у методі Up () буде такий рядок:

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

Якщо ви просто видалите cascadeDelete з кінця, він буде працювати.


5

Тільки для цілей документації для когось, хто прийде в майбутньому, цю річ можна вирішити так просто, як це, і за допомогою цього методу ви можете зробити метод, який вимкнувся один раз, і ви могли отримати доступ до свого методу нормально

Додайте цей метод до класу контекстної бази даних:

protected override void OnModelCreating(DbModelBuilder modelBuilder) {
    modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
}

1

Це звучить дивно, і я не знаю чому, але в моєму випадку це сталося тому, що мій ConnectionString використовував ". в атрибуті "джерело даних". Одного разу я змінив його на "localhost", він працював як шарм. Інші зміни не потрібні були.


1

У .NET Core я грав з усіма верхніми відповідями - але без жодного успіху. Я дуже змінив структуру БД і щоразу додавав нові міграції, намагаючисьupdate-database , але отримував ту саму помилку.

Потім я почав remove-migrationпо одному, поки консоль Package Manager не кинула мені виняток:

Міграція '20170827183131 _ ***' вже застосована до бази даних

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

Тому моєю пропозицією було б: очистити всі ваші тимчасові міграції, поки не буде поточного стану БД.


1

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

Натомість мені довелося запустити цю команду, коли поточна структура db є поточною:

Add-Migration Initial -IgnoreChanges

Ймовірно, існує справжня проблема в структурі db, але врятувати світ на крок за часом ...


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

Коли ваша міграція не вдається, вам надається пара варіантів: "Введення обмеження для зовнішнього ключа" FK_dbo.RecommendedBook_dbo.Department_DepartmentID "в таблиці" Рекомендована книга "може спричинити цикли або кілька каскадних шляхів. Вкажіть УВІДКЛЮЧАТИ НЕ ДІЙ чи НА ОНОВЛЕННЯ НЕ ДІЇ, або змініть інші обмеження ЗОВНІШНЬОГО КЛЮЧА. Не вдалося створити обмеження чи індекс. Дивіться попередні помилки. '

Ось приклад використання 'змінити інші обмеження FOREIGN KEY', встановивши «cascadeDelete» на помилкове у файлі міграції, а потім запустіть «update-database».


0

Жодне з вищезгаданих рішень не працювало для мене. Що мені потрібно було зробити - це використати нульовий int (int?) На зовнішньому ключі, який не потрібно (або не нульовий ключ стовпця), а потім видалити деякі мої міграції.

Почніть з видалення міграцій, а потім спробуйте нульовий int.

Проблемою була і модифікація, і модель моделі. Не потрібно було змінювати код.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.