L'introduzione del vincolo FOREIGN KEY può causare cicli o percorsi multipli in cascata - perché?


295

Ho lottato con questo per un po 'e non riesco proprio a capire cosa sta succedendo. Ho un'entità Carta che contiene i Lati (di solito 2) - e sia le Carte che i Lati hanno uno Stage. Sto usando EF Codefirst migrations e le migrazioni non riescono con questo errore:

L'introduzione del vincolo FOREIGN KEY 'FK_dbo.Sides_dbo.Cards_CardId' nella tabella 'Lati' può causare cicli o percorsi multipli a cascata. Specificare ON DELETE NO ACTION o ON UPDATE NO ACTION o modificare altri vincoli ESTERI CHIAVE.

Ecco l' entità della mia carta :

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

Ecco la mia entità 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; }

}

Ed ecco la mia entità 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; } }
}

La cosa strana è che se aggiungo quanto segue alla mia classe Stage:

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

La migrazione viene eseguita correttamente. Se apro SSMS e guardo le tabelle, posso vedere che Stage_StageIdè stato aggiunto a Cards(come previsto / desiderato), tuttavia Sidesnon contiene alcun riferimento a Stage(non previsto).

Se poi aggiungo

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

Alla mia classe Side, vedo la StageIdcolonna aggiunta alla mia Sidetabella.

Funziona, ma ora in tutta la mia applicazione, ogni riferimento a Stagecontiene a SideId, che in alcuni casi è totalmente irrilevante. Vorrei solo dare a my Carded Sideentity una Stageproprietà basata sulla classe Stage sopra senza inquinare la classe stage con proprietà di riferimento, se possibile ... cosa sto facendo di sbagliato?


7
Disabilita l'eliminazione a cascata consentendo valori nulli nei riferimenti ... quindi in SideClasse aggiungi intero Nullable e rimuovi [Required]attributo =>public int? CardId { get; set; }
Jaider

2
In EF Core, è necessario disabilitare l'eliminazione in cascata con DeleteBehavior.Restricto DeleteBehavior.SetNull.
Sina Lotfi,

Risposte:


371

Perché Stageè richiesto , tutte le relazioni uno-a-molti in cui Stageè coinvolto avranno l'eliminazione a cascata abilitata per impostazione predefinita. Significa che se cancelli Stageun'entità

  • l'eliminazione passerà direttamente a Side
  • l'eliminazione passerà direttamente a Carde perché Carde Sideavendo una relazione uno-a-molti richiesta con l'eliminazione a cascata abilitata di nuovo di default passerà poi da CardaSide

Quindi, hai due percorsi di eliminazione a cascata da Stagea Side- che causano l'eccezione.

È necessario rendere Stagefacoltativo in almeno una delle entità (ovvero rimuovere l' [Required]attributo dalle Stageproprietà) o disabilitare l'eliminazione a cascata con l'API fluente (non possibile con le annotazioni dei dati):

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

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

2
Grazie Slauma. Se utilizzo l'API fluente come hai dimostrato sopra, altri campi manterranno il loro comportamento di eliminazione in cascata? Ho ancora bisogno di Lati da cancellare quando le carte vengono eliminate, per esempio.
SB2055,

1
@ SB2055: Sì, influirà solo sulle relazioni da Stage. Altre relazioni rimangono invariate.
Slauma,

2
C'è un modo per sapere quali proprietà stanno causando l'errore? Sto avendo lo stesso problema e, guardando le mie lezioni, non riesco a vedere dove si trova il ciclo
Rodrigo Juarez,

4
È una limitazione nella loro attuazione? Mi sembra bene per una Stagecancellazione da cascata Sidesia direttamente che attraverso unCard
aaaaaa

1
Supponiamo di impostare CascadeOnDelete su false. Quindi abbiamo rimosso un record di palcoscenico correlato con uno dei record della carta. Cosa succede a Card.Stage (FK)? Rimane lo stesso? o è impostato su Null?
ninbit

61

Avevo un tavolo che aveva una relazione circolare con gli altri e stavo ottenendo lo stesso errore. Si scopre che si tratta della chiave esterna che non era nullable. Se la chiave non è nulla, l'oggetto correlato deve essere eliminato e le relazioni circolari non lo consentono. Quindi usa una chiave esterna nullable.

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

5
Ho rimosso il tag [Obbligatorio] ma un'altra cosa importante era usare int?invece di intrenderlo nullable.
VSB

1
Ho provato molti modi diversi di disattivare l'eliminazione in cascata e niente ha funzionato: questo è stato risolto!
ambog36,

5
Non dovresti farlo se non vuoi consentire a Stage di essere impostato su null (Stage era un campo obbligatorio nella domanda originale).
cfwall

35

Qualcuno si chiede come farlo nel nucleo 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
Ciò disattiva l'eliminazione a cascata su tutte le relazioni. L'eliminazione a cascata può essere una caratteristica desiderata per alcuni casi d'uso.
Blaze,

15
In alternativa,builder.HasOne(x => x.Stage).WithMany().HasForeignKey(x => x.StageId).OnDelete(DeleteBehavior.Restrict);
Biscotti

@Biscuits O i metodi di estensione sono cambiati nel tempo o hai dimenticato il builder _ .Entity<TEntity>() _precedente HasOne() può essere chiamato ...
ViRuSTriNiTy

1
@ViRuSTriNiTy, il mio frammento ha 2 anni. Ma penso che tu abbia ragione: oggi lo sarebbe per quando scegli di implementare IEntityTypeConfiguration<T>. Non ricordo di aver visto il builder.Entity<T>metodo in quei giorni, ma potrei sbagliarmi. Tuttavia, funzioneranno entrambi :)
Biscotti,

21

Stavo ottenendo questo errore per molte entità durante la migrazione da un modello EF7 a una versione EF6. Non volevo passare attraverso ciascuna entità una alla volta, quindi ho usato:

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

2
Questo dovrebbe essere aggiunto nelle classi che ereditano da DbContext, ad esempio nel metodo OnModelCreating. Il costruttore è di tipo DbModelBuilder
CodingYourLife

Questo ha funzionato per me; .NET 4.7, EF 6. Un ostacolo è stato che ho ricevuto l'errore, quindi quando mi sono rigenerato dallo script di migrazione con queste convenzioni rimosse, non mi è sembrato utile. L'esecuzione di "Add-Migration" con "-Force" ha cancellato tutto e ricostruito includendo queste convenzioni sopra. Problema risolto ...
James Joyce,

Quelli non esistono nel core .net, nessun equivalente lì?
jjxtra,


20

Puoi impostare cascadeDelete su false o true (nel tuo metodo Up () di migrazione). Dipende dal vostro requisito.

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

2
@Mussakkhir grazie per la tua risposta. La tua strada è molto elegante e più finita: è più accurata e mirata direttamente al problema che ho affrontato!
Nozim Turakulov,

Non dimenticare che il UPmetodo potrebbe essere modificato da operazioni esterne.
Demenza

8

In .NET Core ho modificato l'opzione onDelete in 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

Ho avuto anche questo problema, l'ho risolto istantaneamente con questa risposta da un thread simile

Nel mio caso, non volevo cancellare il record dipendente dalla cancellazione dei tasti. Se questo è il caso nella tua situazione, modifica semplicemente il valore booleano nella migrazione in false:

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

È probabile che, se si creano relazioni che generano questo errore del compilatore, ma si desidera mantenere l'eliminazione a cascata; hai un problema con le tue relazioni.


6

Ho risolto questo. Quando aggiungi la migrazione, nel metodo Su () ci sarà una linea come questa:

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

Se elimini semplicemente cascadeDelete dalla fine funzionerà.


5

Solo a scopo di documentazione, per qualcuno che viene nel futuro, questa cosa può essere risolta in modo semplice come questo, e con questo metodo, potresti fare un metodo disabilitato una volta e puoi accedere al tuo metodo normalmente

Aggiungi questo metodo alla classe del database di contesto:

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

1

Sembra strano e non so perché, ma nel mio caso ciò stava accadendo perché il mio ConnectionString utilizzava "." nell'attributo "origine dati". Una volta cambiato in "localhost" ha funzionato come un fascino. Non sono stati necessari altri cambiamenti.


1

In .NET Core ho giocato con tutte le risposte migliori, ma senza successo. Ho apportato molte modifiche alla struttura del DB e ogni volta ho aggiunto nuovi tentativi di migrazioneupdate-database , ma ricevuto lo stesso errore.

Poi ho iniziato a remove-migrationuno a uno fino a quando la Console di gestione pacchetti non mi ha lanciato un'eccezione:

La migrazione '20170827183131 _ ***' è già stata applicata al database

Successivamente, ho aggiunto la nuova migrazione ( add-migration) e update-database correttamente

Quindi il mio suggerimento sarebbe: cancellare tutte le migrazioni temporanee, fino allo stato DB corrente.


1

Le risposte esistenti sono fantastiche. Volevo solo aggiungere che ho riscontrato questo errore a causa di un motivo diverso. Volevo creare una migrazione EF iniziale su un DB esistente ma non ho usato il flag -IgnoreChanges e ho applicato il comando Update-Database su un database vuoto (anche sugli errori esistenti).

Invece ho dovuto eseguire questo comando quando l'attuale struttura db è quella corrente:

Add-Migration Initial -IgnoreChanges

C'è probabilmente un vero problema nella struttura del db ma salva il mondo un passo alla volta ...


1

Il modo più semplice è modificare il file di migrazione (cascadeDelete: true) in, (cascadeDelete: false)quindi dopo aver assegnato il comando Update-Database nella console di Package Manager. Se è un problema con l'ultima migrazione, allora va bene. Altrimenti controlla la tua precedente cronologia delle migrazioni, copia quelle cose, incolla nel tuo ultimo file di migrazione, dopodiché fai la stessa cosa. funziona perfettamente per me.


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

Quando la migrazione non riesce, vengono fornite alcune opzioni: "Introduzione del vincolo FOREIGN KEY" FK_dbo.RecommendedBook_dbo.Department_DepartmentID "nella tabella" RecommendedBook "può causare cicli o percorsi a cascata multipli. Specificare ON DELETE NO ACTION o ON UPDATE NO ACTION o modificare altri vincoli ESTERI CHIAVE. Impossibile creare vincolo o indice. Vedi gli errori precedenti ".

Ecco un esempio dell'uso di "modifica altri vincoli FOREIGN KEY" impostando "cascadeDelete" su false nel file di migrazione ed eseguendo "update-database".


0

Nessuna delle soluzioni di cui sopra ha funzionato per me. Quello che dovevo fare era usare un int nullable (int?) Sulla chiave esterna che non era richiesto (o non una chiave di colonna non nulla) e quindi eliminare alcune delle mie migrazioni.

Inizia eliminando le migrazioni, quindi prova l'int nullable.

Il problema era sia una modifica che una progettazione del modello. Non è stato necessario modificare il codice.


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.