414 votes

Présenter une contrainte de clé étrangère peut provoquer des cycles ou plusieurs chemins de cascade - pourquoi?

Je me suis débattu avec cela depuis un certain temps et je n'arrive pas à comprendre ce qui se passe. J'ai une entité Card qui contient des Sides (généralement 2) - et à la fois les Cards et les Sides ont un Stage. J'utilise des migrations EF Codefirst et les migrations échouent avec cette erreur :

L'introduction de la contrainte de clé étrangère 'FK_dbo.Sides_dbo.Cards_CardId' sur la table 'Sides' peut entraîner des cycles ou des chemins de cascade multiples. Spécifiez ON DELETE NO ACTION ou ON UPDATE NO ACTION, ou modifiez d'autres contraintes de clé étrangère.

Voici mon entité Card :

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

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

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

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

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

}

Et voici mon 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 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; } }
}

Le problème est que si j'ajoute ce qui suit à ma classe Stage :

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

La migration s'exécute avec succès. Si j'ouvre SSMS et regarde les tables, je peux voir que Stage_StageId a été ajouté à Cards (comme prévu/souhaité), cependant Sides ne contient aucune référence à Stage (pas attendue).

Si j'ajoute ensuite :

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

À ma classe Side, je vois que la colonne StageId est ajoutée à ma table Side.

Cela fonctionne, mais maintenant dans toute mon application, toute référence à Stage contient un SideId, qui est dans certains cas totalement inapproprié. J'aimerais simplement donner à mes entités Card et Side une propriété Stage basée sur la classe Stage citée ci-dessus sans polluer la classe Stage avec des propriétés de référence si possible... que suis-je en train de faire de mal ?

8 votes

Désactiver la suppression en cascade en autorisant les valeurs nulles dans les références... donc dans la classe Side ajouter un entier Nullable et supprimer l'attribut [Required] => public int? CardId { get; set; }

4 votes

Dans EF Core, vous devez désactiver la suppression en cascade avec DeleteBehavior.Restrict ou DeleteBehavior.SetNull.

7 votes

La réponse acceptée est la seule réponse correcte. La question est : comment empêcher un chemin de cascade circulaire si je veux une relation obligatoire. Une seule instruction de mappage suffit. Donc, ne suggérez pas de rendre la relation optionnelle, ou pire, de modifier le fichier de migration généré (introduisant une divergence entre le modèle de base de données et le modèle conceptuel), ou pire encore, de désactiver toutes les suppressions en cascade.

0voto

DonP Points 71

Aucune des solutions mentionnées n'a fonctionné pour moi. Ce que j'ai dû faire était d'utiliser un int nullable (int?) sur la clé étrangère qui n'était pas requise (ou pas une clé de colonne non nulle) puis de supprimer certaines de mes migrations.

Commencez par supprimer les migrations, puis essayez l'int nullable.

Le problème était à la fois une modification et une conception de modèle. Aucun changement de code n'était nécessaire.

0voto

La manière simple consiste à modifier votre fichier de migration (cascadeDelete: true) en (cascadeDelete: false), puis attribuez la commande Update-Database dans votre Package Manager Console. Si le problème se trouve dans votre dernière migration, alors tout va bien. Sinon, vérifiez l'historique de vos migrations précédentes, copiez ces éléments, collez-les dans votre dernier fichier de migration, puis répétez la même opération. Cela a parfaitement fonctionné pour moi.

0voto

FelixAVeras Points 433

Vous pourriez ajouter ceci dans votre DataContext.cs, cela fonctionne pour moi...

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

0voto

Dans .NET Core et Entity Framework Core, j'ai changé l'option onDelete en ReferencialAction.Restrict

-3voto

J'ai rencontré le même problème et je suis resté bloqué pendant longtemps. Les étapes suivantes m'ont sauvé. Parcourez les contraintes et changez l'onDelete ReferentialAction en NoAction à la place de Cascade

  constraints: table =>
  {
      table.PrimaryKey("PK_table1", x => x.Id);
      table.ForeignKey(
         name: "FK_table1_table2_table2Id",
         column: x => x.table2Id,
         principalTable: "table2",
         principalColumn: "Id",
         onDelete: ReferentialAction.NoAction);
  });

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X