L'introduction de la contrainte FOREIGN KEY peut provoquer des cycles ou plusieurs chemins de cascade - pourquoi?

295

Je lutte avec ça depuis un moment et je n'arrive pas à comprendre ce qui se passe. J'ai une entité Carte qui contient des côtés (généralement 2) - et les cartes et les côtés ont une étape. J'utilise les migrations EF Codefirst et les migrations échouent avec cette erreur:

L'introduction de la contrainte FOREIGN KEY 'FK_dbo.Sides_dbo.Cards_CardId' sur la table 'Sides' peut provoquer des cycles ou plusieurs chemins de cascade. Spécifiez ON DELETE NO ACTION ou ON UPDATE NO ACTION, ou modifiez d'autres contraintes FOREIGN KEY.

Voici mon entité Carte :

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

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

Ce qui est étrange, c'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 a réussi. Si j'ouvre SSMS et regarde les tableaux, je peux voir qu'il Stage_StageIda été ajouté à Cards(comme prévu / souhaité), mais Sidesne contient aucune référence à Stage(non attendu).

Si j'ajoute ensuite

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

Pour ma classe Side, je vois une StageIdcolonne ajoutée à ma Sidetable.

Cela fonctionne, mais maintenant tout au long de mon application, toute référence à Stagecontient un SideId, qui est dans certains cas totalement hors de propos. Je voudrais juste donner à mes entités Cardet Sideune Stagepropriété basée sur la classe Stage ci-dessus sans polluer la classe Stage avec des propriétés de référence si possible ... que fais-je de mal?

SB2055
la source
7
Désactivez la suppression en cascade en autorisant les valeurs nulles dans les références ... donc dans la Sideclasse, ajoutez un entier nul et supprimez l' [Required]attribut =>public int? CardId { get; set; }
Jaider
2
Dans EF Core, vous devez désactiver la suppression en cascade avec DeleteBehavior.Restrictou DeleteBehavior.SetNull.
Sina Lotfi

Réponses:

371

Parce que cela Stageest requis , toutes les relations un-à-plusieurs où Stageest impliqué auront la suppression en cascade activée par défaut. Cela signifie que si vous supprimez une Stageentité

  • la suppression se répercutera directement sur Side
  • la suppression sera mise en cascade directement vers Cardet parce que Cardet Sideavec une relation un-à-plusieurs requise avec la suppression en cascade activée à nouveau par défaut, elle sera ensuite mise en cascade de CardàSide

Ainsi, vous avez deux chemins de suppression en cascade de Stageà Side- ce qui provoque l'exception.

Vous devez soit rendre l' Stageoption facultative dans au moins une des entités (c'est-à-dire supprimer l' [Required]attribut des Stagepropriétés) ou désactiver la suppression en cascade avec l'API Fluent (impossible avec les annotations de données):

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

modelBuilder.Entity<Side>()
    .HasRequired(s => s.Stage)
    .WithMany()
    .WillCascadeOnDelete(false);
Slauma
la source
2
Merci Slauma. Si j'utilise l'API couramment comme vous l'avez démontré ci-dessus, les autres champs conserveront-ils leur comportement de suppression en cascade? Par exemple, j'ai encore besoin de supprimer des côtés lorsque des cartes sont supprimées.
SB2055
1
@ SB2055: Oui, cela n'affectera que les relations de Stage. Les autres relations restent inchangées.
Slauma
2
Existe-t-il un moyen de savoir quelles propriétés sont à l'origine de l'erreur? J'ai le même problème et en regardant mes cours, je ne vois pas où est le cycle
Rodrigo Juarez
4
Est-ce une limitation dans leur mise en œuvre? Cela me semble bien pour une Stagesuppression à cascade à la Sidefois directement et via unCard
aaaaaa
1
Supposons que nous définissions CascadeOnDelete sur false. Ensuite, nous avons supprimé un enregistrement d'étape qui est lié à l'un des enregistrements de la carte. Qu'advient-il de Card.Stage (FK)? Reste-t-il le même? ou est-il réglé sur Null?
ninbit
61

J'avais une table qui avait une relation circulaire avec les autres et j'obtenais la même erreur. Il s'avère qu'il s'agit de la clé étrangère qui n'était pas annulable. Si la clé n'est pas annulable, l'objet lié doit être supprimé et les relations circulaires ne le permettent pas. Utilisez donc une clé étrangère nullable.

[ForeignKey("StageId")]
public virtual Stage Stage { get; set; }
public int? StageId { get; set; }
Cem Mutlu
la source
5
J'ai supprimé la balise [Obligatoire] mais une autre chose importante était d'utiliser int?au lieu de la intlaisser nullable.
VSB
1
J'ai essayé de nombreuses façons de désactiver la suppression en cascade et rien n'a fonctionné - cela a résolu le problème!
ambog36
5
Vous ne devriez pas faire cela si vous ne souhaitez pas autoriser la mise à null de Stage (Stage était un champ obligatoire dans la question d'origine).
cfwall
35

Quiconque se demande comment le faire dans EF core:

      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
la source
3
Cela désactiverait la suppression en cascade de toutes les relations. La suppression en cascade peut être une fonctionnalité souhaitée pour certains cas d'utilisation.
Blaze
15
Alternativement,builder.HasOne(x => x.Stage).WithMany().HasForeignKey(x => x.StageId).OnDelete(DeleteBehavior.Restrict);
Biscuits
@Biscuits Soit les méthodes d'extension ont changé avec le temps, soit vous avez oublié ce qui builder _ .Entity<TEntity>() _précède HasOne() peut être appelé ...
ViRuSTriNiTy
1
@ViRuSTriNiTy, mon extrait de code a 2 ans. Mais, je pense que vous avez raison - de nos jours, ce serait pour quand vous opterez pour la mise en œuvre IEntityTypeConfiguration<T>. Je ne me souviens pas avoir vu la builder.Entity<T>méthode ces jours-là, mais je peux me tromper. Néanmoins, ils fonctionneront tous les deux :)
Biscuits
21

J'obtenais cette erreur pour de nombreuses entités lorsque je migrais d'un modèle EF7 vers une version EF6. Je ne voulais pas avoir à passer par chaque entité une par une, alors j'ai utilisé:

builder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
builder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
Sean
la source
2
Cela doit être ajouté dans la ou les classes héritant de DbContext, par exemple dans la méthode OnModelCreating. Le générateur est de type DbModelBuilder
CodingYourLife
Cela a fonctionné pour moi; .NET 4.7, EF 6. Une pierre d'achoppement était que j'ai eu l'erreur, donc quand j'ai régénéré par le script de migration avec ces conventions supprimées, cela n'a pas semblé aider. L'exécution de "Add-Migration" avec "-Force" a tout effacé et reconstruit, y compris les conventions ci-dessus. Problème résolu ...
James Joyce
Ceux-ci n'existent pas dans le noyau .net, y a-t-il un équivalent?
jjxtra
20

Vous pouvez définir cascadeDelete sur false ou true (dans votre méthode de migration Up ()). Dépend de votre condition.

AddForeignKey("dbo.Stories", "StatusId", "dbo.Status", "StatusID", cascadeDelete: false);
Musakkhir Sayyed
la source
2
@Mussakkhir merci pour votre réponse. Votre chemin est très élégant et plus terminé - il est plus précis et ciblé directement sur le problème que j'ai rencontré!
Nozim Turakulov
N'oubliez pas que la UPméthode peut être modifiée par des opérations externes.
Dementic
8

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

J'ai aussi 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 clé. Si c'est le cas dans votre situation, changez simplement la valeur booléenne de la migration en false:

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

Il y a de fortes chances 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.

jonc.js
la source
6

J'ai corrigé ça. 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 le cascadeDelete de la fin, cela fonctionnera.

Usman Khan
la source
5

Juste à des fins de documentation, pour quelqu'un qui vient dans le futur, cette chose peut être résolue aussi simple que cela, et avec cette méthode, vous pouvez faire une méthode qui s'est désactivée une fois, et vous pouvez accéder à votre méthode normalement

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

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

Cela semble bizarre et je ne sais pas pourquoi, mais dans mon cas, cela se produisait parce que mon ConnectionString utilisait "." dans l'attribut "source de données". Une fois que je l'ai changé en "localhost", cela a fonctionné comme un charme. Aucun autre changement n'était nécessaire.

Marco Alves
la source
1

Dans .NET Core, j'ai joué avec toutes les réponses supérieures - mais sans succès. J'ai beaucoup changé la structure de la base de données et à chaque fois j'ai ajouté une nouvelle migration essayant deupdate-database , mais reçu la même erreur.

Ensuite, j'ai commencé remove-migrationun par un 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 update-database réussi

Donc, ma suggestion serait: effacez toutes vos migrations temporaires, jusqu'à votre état actuel de la base de données.

rock_walker
la source
1

Les réponses existantes sont excellentes. Je voulais juste ajouter que j'ai rencontré cette erreur pour une raison différente. Je voulais créer une migration EF initiale sur une base de données existante mais je n'ai pas utilisé les -IgnoreChanges indicateur et appliqué la commande Update-Database sur une base de données vide (également sur les échecs existants).

Au lieu de cela, j'ai dû exécuter cette commande lorsque la structure de base de données actuelle est la suivante:

Add-Migration Initial -IgnoreChanges

Il y a probablement un vrai problème dans la structure de la base de données mais sauvez le monde une étape à la fois ...

CodingYourLife
la source
1

Le moyen le plus simple consiste à, éditez votre fichier de migration (cascadeDelete: true)dans (cascadeDelete: false)puis après affecter la commande Update-Database dans votre console du gestionnaire de package. Sinon, vérifiez votre historique de migration précédent, copiez ces éléments, collez-les dans votre dernier fichier de migration, puis faites la même chose. cela fonctionne parfaitement pour moi.

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

Lorsque votre migration échoue, deux options vous sont proposées: 'Introduction de la contrainte FOREIGN KEY' FK_dbo.RecommendedBook_dbo.Department_DepartmentID 'sur la table' RecommendedBook 'peut provoquer des cycles ou plusieurs chemins de cascade. Spécifiez ON DELETE NO ACTION ou ON UPDATE NO ACTION, ou modifiez d'autres contraintes FOREIGN KEY. Impossible de créer une contrainte ou un index. Voir les erreurs précédentes. '

Voici un exemple d'utilisation de la «modification d'autres contraintes FOREIGN KEY» en définissant «cascadeDelete» sur false dans le fichier de migration, puis exécutez «update-database».

Christopher Govender
la source
0

Aucune des solutions susmentionnées n'a fonctionné pour moi. Ce que je devais 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 le nullable int.

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

Ayson Baxter
la source
-1

Rendez vos attributs de clé étrangère nullables. Ça marchera.

Umair Javed
la source
1
que la réponse dans les commentaires sous les questions s'il vous plaît développez là
Kostia Mololkin