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.

25voto

Mike Jones Points 41

Dans .NET Core, j'ai changé l'option onDelete en 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);
            });

13voto

jonc.js Points 358

J'ai également eu ce problème, je l'ai résolu instantanément avec cette réponse d'un fil similaire

Dans mon cas, je ne voulais pas supprimer l'enregistrement dépendant lors de la suppression de la clé. Si c'est le cas dans votre situation, il suffit de changer la valeur booléenne dans la migration en false :

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

Il est probable que, si vous créez des relations qui génèrent cette erreur de compilation mais que vous souhaitez conserver la suppression en cascade, vous avez un problème avec vos relations.

10voto

Usman Khan Points 562

J'ai corrigé cela. Lorsque vous ajoutez la migration, dans la méthode Up() il y aura une ligne comme celle-ci :

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

Si vous supprimez simplement cascadeDelete à la fin, cela fonctionnera.

7voto

sgrysoft Points 66

Juste à des fins de documentation, pour quelqu'un qui viendra dans le futur, cette chose peut être résolue aussi simplement que ceci, et avec cette méthode, vous pourriez désactiver une méthode une fois, et vous pourriez accéder à votre méthode normalement

Ajoutez cette méthode à la classe de base de données du contexte :

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

6voto

rock_walker Points 70

Dans .NET Core, j'ai joué avec toutes les réponses ci-dessus - mais sans aucun succès. J'ai apporté de nombreux changements à la structure de la base de données et à chaque fois j'ai ajouté une nouvelle migration en tentant de update-database, mais j'ai reçu la même erreur.

Ensuite, j'ai commencé à remove-migration une par une jusqu'à ce que la Console du Gestionnaire de Packages me lance une exception :

La migration '20170827183131_***' a déjà été appliquée à la base de données

Après cela, j'ai ajouté une nouvelle migration (add-migration) et j'ai réussi à update-database avec succès

Je suggère donc de supprimer toutes vos migrations temporaires, jusqu'à l'état actuel de votre base de données.

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