Wprowadzenie ograniczenia KLUCZ OBCY może powodować cykle lub wiele ścieżek kaskadowych - dlaczego?


295

Zmagam się z tym od dłuższego czasu i nie mogę do końca zrozumieć, co się dzieje. Mam encję Karty, która zawiera Strony (zwykle 2) - a obie Karty i Strony mają scenę. Korzystam z migracji EF Codefirst i migracje kończą się niepowodzeniem z powodu tego błędu:

Wprowadzenie ograniczenia OBCEGO KLUCZA „FK_dbo.Sides_dbo.Cards_CardId” w tabeli „Strony” może powodować cykle lub wiele ścieżek kaskadowych. Określ NA USUŃ BRAK AKCJI lub NA AKTUALIZACJĘ BEZ AKCJI, lub zmodyfikuj inne KLUCZE OBCE.

Oto moja karta Card :

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

Oto moja jednostka Side :

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

}

A oto moja jednostka Stage :

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

Dziwne jest to, że jeśli dodam następujące elementy do mojej klasy Stage:

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

Migracja przebiega pomyślnie. Jeśli otworzę SSMS i spojrzę na tabele, widzę, że Stage_StageIdzostał dodany do Cards(zgodnie z oczekiwaniami / pożądaniami), jednak Sidesnie zawiera odniesienia do Stage(nie oczekiwano).

Jeśli to dodam

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

Do mojej klasy Side widzę StageIdkolumnę dodaną do mojej Sidetabeli.

Działa to, ale teraz w mojej aplikacji każde odniesienie do Stagezawiera SideId, co w niektórych przypadkach jest całkowicie nieistotne. Chciałbym po prostu nadać mojemu Cardi Sidepodmiotom Stagewłaściwość opartą na powyższej klasie Stage bez zanieczyszczania klasy stage właściwościami referencyjnymi, jeśli to możliwe ... co robię źle?


7
Wyłącz kaskadowe usuwanie, zezwalając na wartości zerowe w referencjach ... więc w Sideklasie dodaj Nullable integer i usuń [Required]atrybut =>public int? CardId { get; set; }
Jaider

2
W EF Core powinieneś wyłączyć usuwanie kaskadowe za pomocą DeleteBehavior.Restrictlub DeleteBehavior.SetNull.
Sina Lotfi

Odpowiedzi:


371

Ponieważ Stagejest to wymagane , wszystkie relacje jeden-do-wielu, w których Stagejest zaangażowana, będą domyślnie włączone kaskadowe usuwanie. Oznacza to, że jeśli usuniesz Stagejednostkę

  • skasowanie nastąpi kaskadowo bezpośrednio do Side
  • usuwanie będzie kaskadowe bezpośrednio do Cardi ponieważ Cardi będzie Sidemieć wymaganą relację jeden-do-wielu z domyślnie włączoną funkcją usuwania kaskadowego, a następnie kaskadowo od CarddoSide

Tak więc masz dwie kaskadowe ścieżki usuwania od Stagedo Side- co powoduje wyjątek.

Musisz albo ustawić Stageopcję opcjonalną w co najmniej jednym obiekcie (tj. Usunąć [Required]atrybut z Stagewłaściwości), albo wyłączyć kaskadowe usuwanie za pomocą Fluent API (nie jest to możliwe z adnotacjami danych):

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

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

2
Dzięki, Slauma. Jeśli użyję płynnego interfejsu API, jak wykazano powyżej, czy inne pola zachowają swoje zachowanie kasowania? Nadal muszę na przykład usuwać Strony podczas usuwania kart.
SB2055

1
@ SB2055: Tak, wpłynie to tylko na relacje z Stage. Inne relacje pozostają niezmienione.
Slauma,

2
Czy jest jakiś sposób, aby wiedzieć, które właściwości powodują błąd? Mam ten sam problem i patrząc na moje zajęcia nie widzę, gdzie jest cykl
Rodrigo Juarez

4
Czy jest to ograniczenie w ich wdrażaniu? Wydaje mi się, że mogę StageSideCard
usunąć

1
Załóżmy, że ustawiliśmy CascadeOnDelete na false. Następnie usunęliśmy rekord etapowy związany z jednym z rekordów karty. Co dzieje się z Card.Stage (FK)? Czy to pozostaje takie samo? czy jest ustawiony na Null?
ninbit

61

Miałem stół, który miał okrągły związek z innymi i otrzymywałem ten sam błąd. Okazuje się, że chodzi o klucz obcy, który nie był zerowalny. Jeśli klucz nie ma wartości zerowej, obiekt powiązany musi zostać usunięty, a relacje cykliczne na to nie pozwalają. Więc użyj zerowego klucza obcego.

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

5
Usunąłem tag [Wymagane], ale inną ważną rzeczą było użyć int?zamiast intpozwolić, aby był zerowalny.
VSB

1
Próbowałem wielu różnych sposobów wyłączania kasowania kasowania i nic nie działało - to naprawiło!
ambog36

5
Nie powinieneś tego robić, jeśli nie chcesz zezwalać na ustawienie Stage na null (Stage było wymaganym polem w pierwotnym pytaniu).
cfwall

35

Każdy, kto zastanawia się, jak to zrobić w rdzeniu 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
To wyłączyłoby kaskadowe usuwanie wszystkich relacji. Usunięcie kaskadowe może być pożądaną funkcją w niektórych przypadkach użycia.
Blaze

15
Alternatywniebuilder.HasOne(x => x.Stage).WithMany().HasForeignKey(x => x.StageId).OnDelete(DeleteBehavior.Restrict);
Ciastka

@ Biszkopty Z czasem metody rozszerzenia uległy zmianie lub zapomniałeś, że builder _ .Entity<TEntity>() _wcześniej HasOne() można je nazwać ...
ViRuSTriNiTy

1
@ViRuSTriNiTy, mój fragment ma 2 lata. Ale myślę, że masz rację - w tej chwili byłoby to możliwe, kiedy zdecydujesz się na wdrożenie IEntityTypeConfiguration<T>. Nie przypominam sobie, aby widziałem tę builder.Entity<T>metodę w tych dniach, ale mogę się mylić. Niemniej jednak oba będą działać :)
Herbatniki

21

Ten błąd pojawiał się w przypadku wielu jednostek podczas migracji z modelu EF7 do wersji EF6. Nie chciałem przechodzić przez każdą jednostkę pojedynczo, więc użyłem:

builder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
builder.Conventions.Remove<OneToManyCascadeDeleteConvention>();

2
Należy to dodać w klasach dziedziczących po DbContext, np. W metodzie OnModelCreating. Konstruktor jest typu DbModelBuilder
CodingYourLife

To zadziałało dla mnie; .NET 4.7, EF 6. Jedną z przeszkód był błąd, więc kiedy zregenerowałem się za pomocą skryptu migracji z usuniętymi konwencjami, nie pojawił się POMOC. Uruchomienie „Add-Migration” z „-Force” wyczyściło to wszystko i przebudowało, włączając powyższe konwencje. Problem rozwiązany ...
James Joyce,

Nie istnieją one w rdzeniu .net, czy istnieje tam odpowiednik?
jjxtra


20

Możesz ustawić cascadeDelete na false lub true (w metodzie Up () migracji). Zależy od twoich wymagań.

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

2
@Mussakkhir dziękuję za odpowiedź. Twoja droga jest bardzo elegancka i bardziej szczegółowa - jest bardziej dokładna i ukierunkowana bezpośrednio na problem, z którym się spotkałem!
Nozim Turakulov

Tylko nie zapomnij, że UPmetoda może zostać zmodyfikowana przez operacje zewnętrzne.
Dementyczny,

8

W .NET Core zmieniłem opcję onDelete na 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

Miałem również ten problem, rozwiązałem go natychmiast dzięki tej odpowiedzi z podobnego wątku

W moim przypadku nie chciałem usuwać zależnego rekordu po usunięciu klucza. Jeśli tak jest w twojej sytuacji, po prostu zmień wartość logiczną w migracji na false:

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

Szanse są, jeśli tworzysz relacje, które generują ten błąd kompilatora, ale NIE chcesz zachować kaskadowego usuwania; masz problem ze swoimi relacjami.


6

Naprawiłem to. Po dodaniu migracji w metodzie Up () pojawi się następujący wiersz:

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

Jeśli po prostu usuniesz cascadeDelete od końca, będzie działać.


5

Dla celów dokumentacyjnych, dla kogoś, kto przyjdzie w przyszłości, to można rozwiązać tak prosto, jak to, a dzięki tej metodzie możesz zrobić metodę, która została jednorazowo wyłączona, i możesz uzyskać normalny dostęp do tej metody

Dodaj tę metodę do klasy kontekstowej bazy danych:

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

1

Brzmi dziwnie i nie wiem dlaczego, ale w moim przypadku tak się stało, ponieważ mój ConnectionString używał „.” w atrybucie „źródło danych”. Gdy zmieniłem go na „localhost”, działało to jak urok. Żadna inna zmiana nie była potrzebna.


1

W .NET Core grałem ze wszystkimi górnymi odpowiedziami - ale bez powodzenia. Wprowadziłem wiele zmian w strukturze DB i za każdym razem dodawałem nową próbę migracji update-database, ale otrzymywałem ten sam błąd.

Potem zacząłem remove-migrationjeden po drugim, aż konsola Menedżera pakietów rzuciła mi wyjątek:

Migracja „20170827183131 _ ***” została już zastosowana do bazy danych

Następnie dodałem nową migrację ( add-migration) i update-database pomyślnie

Tak więc moja sugestia brzmiałaby: wyczyść wszystkie migracje tymczasowe, aż do obecnego stanu bazy danych.


1

Istniejące odpowiedzi są świetne. Chciałem tylko dodać, że napotkałem ten błąd z innego powodu. Chciałem utworzyć początkową migrację EF na istniejącej bazie danych, ale nie użyłem flagi -IgnoreChanges i zastosowałem polecenie Aktualizuj bazę danych na pustej bazie danych (także w przypadku istniejących awarii).

Zamiast tego musiałem uruchomić tę komendę, gdy bieżąca struktura db jest aktualna:

Add-Migration Initial -IgnoreChanges

Prawdopodobnie istnieje prawdziwy problem w strukturze db, ale ratuj świat krok po kroku ...


1

Prostym sposobem jest, edytować plik migracji (cascadeDelete: true)na (cascadeDelete: false)to po przypisać polecenie Update-bazy danych w menedżerze pakietów Console.if Jest problem z ostatniej migracji ówczesnego porządku. W przeciwnym razie sprawdź swoją wcześniejszą historię migracji, skopiuj te rzeczy, wklej do ostatniego pliku migracji, a następnie zrób to samo. to doskonale dla mnie działa.


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

Gdy migracja się nie powiedzie, pojawi się kilka opcji: „Wprowadzenie ograniczenia KLUCZ ZAGRANICZNY” FK_dbo.ZalecanyBook_dbo.Department_DepartmentID ”w tabeli„ RecommendedBook ”może powodować cykle lub wiele ścieżek kaskadowych. Określ NA USUŃ BRAK AKCJI lub NA AKTUALIZACJĘ BEZ AKCJI, lub zmodyfikuj inne ograniczenia KLUCZA OBCYCH. Nie można utworzyć ograniczenia ani indeksu. Zobacz poprzednie błędy. ”

Oto przykład użycia opcji „modyfikuj inne KLUCZE OBCE” poprzez ustawienie „cascadeDelete” na wartość false w pliku migracji, a następnie uruchomienie „update-database”.


0

Żadne z wyżej wymienionych rozwiązań nie działało dla mnie. Musiałem użyć wartości zerowej int (int?) Na kluczu obcym, który nie był wymagany (lub klucza innej niż null), a następnie usunąć niektóre z moich migracji.

Najpierw usuń migracje, a następnie wypróbuj wartość zerową int.

Problem dotyczył zarówno modyfikacji, jak i projektu modelu. Zmiana kodu nie była konieczna.


Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.