Die Einführung der FOREIGN KEY-Einschränkung kann zu Zyklen oder mehreren Kaskadenpfaden führen - warum?

295

Ich habe eine Weile damit gerungen und kann nicht genau herausfinden, was passiert. Ich habe eine Kartenentität, die Seiten enthält (normalerweise 2) - und sowohl Karten als auch Seiten haben eine Stufe. Ich verwende EF Codefirst-Migrationen und die Migrationen schlagen mit diesem Fehler fehl:

Die Einführung der FOREIGN KEY-Einschränkung 'FK_dbo.Sides_dbo.Cards_CardId' in der Tabelle 'Sides' kann zu Zyklen oder mehreren Kaskadenpfaden führen. Geben Sie ON DELETE NO ACTION oder ON UPDATE NO ACTION an oder ändern Sie andere FOREIGN KEY-Einschränkungen.

Hier ist meine Karte Einheit:

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

Hier ist meine Side Einheit:

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

}

Und hier ist meine Bühnenentität :

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

Was seltsam ist, ist, wenn ich meiner Stage-Klasse Folgendes hinzufüge:

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

Die Migration wird erfolgreich ausgeführt. Wenn ich SSMS öffne und mir die Tabellen ansehe, kann ich sehen, dass Stage_StageIddas hinzugefügt wurde Cards(wie erwartet / gewünscht), Sidesenthält jedoch keinen Verweis aufStage (nicht erwartet) enthält.

Wenn ich dann hinzufüge

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

In meiner Nebenklasse wird eine StageIdSpalte zu meiner hinzugefügtSide Tabelle .

Dies funktioniert, aber jetzt in meiner gesamten Anwendung Stageenthält jeder Verweis auf a SideId, was in einigen Fällen völlig irrelevant ist. Ich möchte meinen Cardund SideEntitäten einfach eine StageEigenschaft geben, die auf der obigen Stage-Klasse basiert, ohne die Stage-Klasse nach Möglichkeit mit Referenzeigenschaften zu verschmutzen ... was mache ich falsch?

SB2055
quelle
7
Deaktivieren Sie das kaskadierende Löschen, indem Sie Nullwerte in den Referenzen Sidezulassen. Fügen Sie also in Class eine Nullable-Ganzzahl hinzu und entfernen Sie das [Required]Attribut =>public int? CardId { get; set; }
Jaider,
2
Im EF Core sollten Sie das Löschen von Kaskaden mit DeleteBehavior.Restrictoder deaktivieren DeleteBehavior.SetNull.
Sina Lotfi

Antworten:

371

Da Stageist erforderlich , alle Eins-zu-viele Beziehungen , in denen Stagebeteiligt ist wird standardmäßig Cascading löschen aktiviert. Dies bedeutet, wenn Sie eine StageEntität löschen

  • Das Löschen wird direkt an kaskadiert Side
  • Das Löschen wird direkt zu Cardund kaskadiert Cardund Sidehat eine erforderliche Eins-zu-Viele-Beziehung zum kaskadierenden Löschen, das standardmäßig wieder aktiviert ist. Es wird dann von Cardzu kaskadierenSide

Sie haben also zwei kaskadierende Löschpfade von Stagebis Side- was die Ausnahme verursacht.

Sie müssen entweder die StageOption in mindestens einer der Entitäten aktivieren (dh das [Required]Attribut aus den StageEigenschaften entfernen ) oder das kaskadierende Löschen mit der Fluent-API deaktivieren (mit Datenanmerkungen nicht möglich):

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

modelBuilder.Entity<Side>()
    .HasRequired(s => s.Stage)
    .WithMany()
    .WillCascadeOnDelete(false);
Slauma
quelle
2
Danke Slauma. Wenn ich eine flüssige API verwende, wie Sie oben gezeigt haben, behalten andere Felder ihr Kaskadenlöschverhalten bei? Ich muss zum Beispiel immer noch Seiten löschen, wenn Karten gelöscht werden.
SB2055
1
@ SB2055: Ja, es wirkt sich nur auf die Beziehungen von aus Stage. Andere Beziehungen bleiben unverändert.
Slauma
2
Gibt es eine Möglichkeit zu wissen, welche Eigenschaften den Fehler verursachen? Ich habe das gleiche Problem und wenn ich mir meine Klassen ansehe, kann ich nicht sehen, wo der Zyklus ist
Rodrigo Juarez
4
Ist dies eine Einschränkung in ihrer Implementierung? Scheint mir in Ordnung zu sein, wenn eine StageLöschung Sidedirekt und durch aCard
aaaaaa
1
Angenommen, wir setzen CascadeOnDelete auf false. Dann haben wir einen Bühnenrekord entfernt, der mit einem der Kartendatensätze zusammenhängt. Was passiert mit Card.Stage (FK)? Bleibt es gleich? oder ist es auf Null gesetzt?
Ninbit
61

Ich hatte einen Tisch, der eine zirkuläre Beziehung zu anderen hatte, und ich bekam den gleichen Fehler. Es stellte sich heraus, dass es sich um den Fremdschlüssel handelt, der nicht nullwertfähig war. Wenn der Schlüssel nicht nullbar ist, muss das zugehörige Objekt gelöscht werden, und Kreisbeziehungen lassen dies nicht zu. Verwenden Sie also einen nullbaren Fremdschlüssel.

[ForeignKey("StageId")]
public virtual Stage Stage { get; set; }
public int? StageId { get; set; }
Cem Mutlu
quelle
5
Ich habe das [Erforderlich] -Tag entfernt, aber es war wichtig, es zu verwenden, int?anstatt intes nullbar zu machen.
VSB
1
Ich habe viele verschiedene Möglichkeiten ausprobiert, um das Löschen von Kaskaden zu deaktivieren, und nichts hat funktioniert - das hat es behoben!
ambog36
5
Sie sollten dies nicht tun, wenn Sie nicht zulassen möchten, dass Stage auf null gesetzt wird (Stage war ein Pflichtfeld in der ursprünglichen Frage).
cfwall
35

Jeder, der sich fragt, wie es im EF-Kern geht:

      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
quelle
3
Das würde das Löschen von Kaskaden für alle Beziehungen deaktivieren. In einigen Anwendungsfällen kann das Löschen von Kaskaden eine gewünschte Funktion sein.
Blaze
15
Alternativbuilder.HasOne(x => x.Stage).WithMany().HasForeignKey(x => x.StageId).OnDelete(DeleteBehavior.Restrict);
Kekse
@Biscuits Entweder sind die Erweiterungsmethoden im Laufe der Zeit geändert oder Sie die forgot builder _ .Entity<TEntity>() _vor HasOne() kann ... genannt werden
ViRuSTriNiTy
1
@ViRuSTriNiTy, mein Snippet ist 2 Jahre alt. Aber ich denke, Sie haben Recht - heutzutage ist es der Zeitpunkt, an dem Sie sich für die Implementierung entscheiden IEntityTypeConfiguration<T>. Ich kann mich nicht erinnern, diese builder.Entity<T>Methode damals gesehen zu haben, aber ich könnte mich irren. Trotzdem werden beide funktionieren :)
Kekse
21

Ich habe diesen Fehler für viele Entitäten erhalten, als ich von einem EF7-Modell auf eine EF6-Version migriert habe. Ich wollte nicht jede Entität einzeln durchgehen müssen, also benutzte ich:

builder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
builder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
Sean
quelle
2
Dies sollte in den Klassen hinzugefügt werden, die von DbContext erben, z. B. in der OnModelCreating-Methode. Der Builder ist vom Typ DbModelBuilder
CodingYourLife
Das hat bei mir funktioniert; .NET 4.7, EF 6. Ein Stolperstein war, dass ich den Fehler erhalten habe. Als ich durch ein Migrationsskript mit diesen Konventionen neu generiert habe, schien es nicht zu helfen. Durch Ausführen der "Add-Migration" mit "-Force" wurde alles gelöscht und einschließlich der oben genannten Konventionen neu erstellt. Problem gelöst ...
James Joyce
Diese gibt es im .net-Kern nicht, gibt es dort ein Äquivalent?
jjxtra
20

Sie können cascadeDelete auf false oder true setzen (in Ihrer Migrationsmethode Up ()). Hängt von Ihrer Anforderung ab.

AddForeignKey("dbo.Stories", "StatusId", "dbo.Status", "StatusID", cascadeDelete: false);
Musakkhir Sayyed
quelle
2
@Mussakkhir danke für deine Antwort. Ihr Weg ist sehr elegant und mehr vorbei - er ist genauer und zielt direkt auf das Problem ab, mit dem ich konfrontiert war!
Nozim Turakulov
Vergessen Sie nicht, dass die UPMethode möglicherweise durch externe Operationen geändert wird.
Dementic
8

In .NET Core habe ich die Option onDelete in ReferencialAction.NoAction geändert

         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);
            });
Mike Jones
quelle
7

Ich hatte dieses Problem auch, ich habe es sofort mit dieser Antwort aus einem ähnlichen Thread gelöst

In meinem Fall wollte ich den abhängigen Datensatz beim Löschen des Schlüssels nicht löschen. Wenn dies in Ihrer Situation der Fall ist, ändern Sie einfach den Booleschen Wert in der Migration in false:

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

Wenn Sie Beziehungen erstellen, die diesen Compilerfehler auslösen, aber die Kaskadenlöschung beibehalten möchten, ist dies wahrscheinlich. Sie haben ein Problem mit Ihren Beziehungen.

jonc.js
quelle
6

Ich habe das behoben. Wenn Sie die Migration hinzufügen, wird in der Up () -Methode eine Zeile wie die folgende angezeigt:

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

Wenn Sie nur cascadeDelete am Ende löschen, funktioniert es.

Usman Khan
quelle
5

Nur zu Dokumentationszwecken kann dieses Problem für jemanden, der in die Zukunft kommt, so einfach gelöst werden. Mit dieser Methode können Sie eine Methode ausführen, die einmal deaktiviert wurde, und Sie können normal auf Ihre Methode zugreifen

Fügen Sie diese Methode der Kontextdatenbankklasse hinzu:

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

Das klingt komisch und ich weiß nicht warum, aber in meinem Fall geschah das, weil mein ConnectionString "." im Attribut "Datenquelle". Nachdem ich es in "localhost" geändert hatte, funktionierte es wie ein Zauber. Es war keine weitere Änderung erforderlich.

Marco Alves
quelle
1

In .NET Core habe ich mit allen oberen Antworten gespielt - aber ohne Erfolg. Ich habe viele Änderungen an der DB-Struktur vorgenommen und jedes Mal neue Migrationsversuche hinzugefügtupdate-database , aber den gleichen Fehler erhalten.

Dann fing ich remove-migrationeins nach dem anderen an, bis die Package Manager-Konsole mir eine Ausnahme warf:

Die Migration '20170827183131 _ ***' wurde bereits auf die Datenbank angewendet

Danach habe ich neue Migration ( add-migration) hinzugefügt und update-database erfolgreich

Mein Vorschlag wäre also: Löschen Sie alle Ihre temporären Migrationen bis zu Ihrem aktuellen DB-Status.

rock_walker
quelle
1

Die vorhandenen Antworten sind großartig. Ich wollte nur hinzufügen, dass ich aus einem anderen Grund auf diesen Fehler gestoßen bin. Ich wollte eine anfängliche EF-Migration für eine vorhandene Datenbank erstellen, habe jedoch die -IgnoreChanges nicht verwendet Datenbank Flag und den Befehl Update-Database auf eine leere Datenbank angewendet (auch bei vorhandenen Fehlern).

Stattdessen musste ich diesen Befehl ausführen, wenn die aktuelle Datenbankstruktur die aktuelle ist:

Add-Migration Initial -IgnoreChanges

Es gibt wahrscheinlich ein echtes Problem in der Datenbankstruktur, aber rette die Welt Schritt für Schritt ...

CodingYourLife
quelle
1

Der einfache Weg ist, Bearbeiten Sie Ihre Migrationsdatei (cascadeDelete: true)in (cascadeDelete: false)dann nach dem Update-Befehl Datenbank in Ihrem Package Manager Console.if es das Problem mit der letzten Migration zugewiesen werden dann alles in Ordnung. Überprüfen Sie andernfalls Ihren früheren Migrationsverlauf, kopieren Sie diese Dinge, fügen Sie sie in Ihre letzte Migrationsdatei ein, und machen Sie danach dasselbe. es funktioniert perfekt für mich.

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

Wenn Ihre Migration fehlschlägt, stehen Ihnen mehrere Optionen zur Verfügung: 'Einführung der FOREIGN KEY-Einschränkung' FK_dbo.RecommendedBook_dbo.Department_DepartmentID 'in Tabelle' RecommendedBook 'kann Zyklen oder mehrere Kaskadenpfade verursachen. Geben Sie ON DELETE NO ACTION oder ON UPDATE NO ACTION an oder ändern Sie andere FOREIGN KEY-Einschränkungen. Einschränkung oder Index konnten nicht erstellt werden. Siehe vorherige Fehler. '

Hier ist ein Beispiel für die Verwendung der Einschränkungen "Andere FOREIGN KEY-Änderungen ändern", indem "cascadeDelete" in der Migrationsdatei auf "false" gesetzt und anschließend "update-database" ausgeführt wird.

Christopher Govender
quelle
0

Keine der oben genannten Lösungen hat bei mir funktioniert. Was ich tun musste, war, ein nullfähiges int (int?) Für den Fremdschlüssel zu verwenden, das nicht erforderlich war (oder keinen nicht null-Spaltenschlüssel), und dann einige meiner Migrationen zu löschen.

Löschen Sie zunächst die Migrationen und versuchen Sie dann das nullable int.

Problem war sowohl eine Modifikation als auch ein Modelldesign. Es war keine Codeänderung erforderlich.

Ayson Baxter
quelle
-1

Machen Sie Ihre Fremdschlüsselattribute nullbar. Das wird funktionieren.

Umair Javed
quelle
1
dass die Antwort in Kommentaren unter Fragen bitte dort
näher