J'ai un projet simple avec un format de blog. Il y a des utilisateurs et des blogs. L'ID utilisateur est présent en tant que clé étrangère dans la table du blog. Requête de création SQL comme suit:

CREATE TABLE BlogUser (
    BlogUser_ID int NOT NULL IDENTITY (1,1) CONSTRAINT PK_User PRIMARY KEY CLUSTERED,
    DisplayName varchar(50) NOT NULL);

CREATE TABLE BlogPost (
    Post_ID int NOT NULL IDENTITY (1,1) CONSTRAINT PK_Blog PRIMARY KEY CLUSTERED,
    BlogUser_ID int NOT NULL CONSTRAINT FK_BlogPost_BlogUser REFERENCES BlogUser(BlogUser_ID),
    BlogTitle varchar(30) NOT NULL,
    BlogDate datetime NOT NULL,
    BlogContent varchar(8000) NOT NULL);

Voici les modèles:

    public partial class BlogPost
    {
        public int PostId { get; set; }
        public int BlogUserId { get; set; }
        public string BlogTitle { get; set; }
        public DateTime BlogDate { get; set; }
        public string BlogContent { get; set; }

        public virtual BlogUser BlogUser { get; set; }
    }
    public partial class BlogUser
    {
        public BlogUser()
        {
            BlogPost = new HashSet<BlogPost>();
        }

        public int BlogUserId { get; set; }
        public string DisplayName { get; set; }

        public virtual ICollection<BlogPost> BlogPost { get; set; }
    }

Voici les modèles de la classe DbContext:

protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            //Blog Post
            modelBuilder.Entity<BlogPost>(entity =>
            {
                entity.HasKey(e => e.PostId).HasName("PK_Blog");

                entity.Property(e => e.PostId).HasColumnName("Post_ID");

                entity.Property(e => e.BlogContent)
                    .IsRequired()
                    .HasMaxLength(8000)
                    .IsUnicode(false);

                entity.Property(e => e.BlogDate).HasColumnType("datetime");

                entity.Property(e => e.BlogTitle)
                    .IsRequired()
                    .HasMaxLength(30)
                    .IsUnicode(false);

                entity.Property(e => e.BlogUserId).HasColumnName("BlogUser_ID");

                entity.HasOne(d => d.BlogUser)
                    .WithMany(p => p.BlogPost)
                    .HasForeignKey(d => d.BlogUserId)
                    .OnDelete(DeleteBehavior.Cascade) //--> What is missing here?
                    .HasConstraintName("FK_BlogPost_BlogUser");
            });

            //Blog User
            modelBuilder.Entity<BlogUser>(entity =>
            {
                entity.Property(e => e.BlogUserId).HasColumnName("BlogUser_ID");

                entity.Property(e => e.DisplayName)
                    .IsRequired()
                    .HasMaxLength(50)
                    .IsUnicode(false);
            });

            OnModelCreatingPartial(modelBuilder);
        }

Si je supprime un utilisateur qui a publié des blogs, j'aimerais que tous les blogs qu'ils publient soient également supprimés sans avoir à écrire un script pour supprimer leurs blogs un par un.

Comme vous pouvez le voir, j'ai placé OnDelete (DeleteBehavior.Cascade) sur la clé étrangère qui relie les deux. Mais lorsque j'essaye de supprimer un utilisateur avec des blogs, je reçois une erreur indiquant «l'instruction DELETE est en conflit avec la contrainte REFERENCE« FK_BlogPost_BlogUser »».

Qu'est-ce que je rate?

C'est en fait pour un projet plus grand où il y a plus de 30 dépendances pour plusieurs tables. Le comportement en cascade est préféré pour cette partie du projet, il est donc logique de demander .NET à Cascade au lieu d'écrire 30 boucles distinctes pour chacune. Idéalement, le comportement en cascade ne devrait être appliqué qu'aux modèles en question et non au projet dans son ensemble.

1
N.MORR 2 sept. 2020 à 09:44

3 réponses

Meilleure réponse

L'ajout de OnDelete(DeleteBehavior.Cascade) ne suffit pas - vous devez refléter ce changement dans la base de données (comme on peut le voir, la contrainte FK existante n'a pas d'option ON DELETE CASCADE) - soit manuellement (puisque la structure du code indique modèle d'entité élaboré à partir d'une base de données existante), ou en générant une migration d'application.

Btw, la suppression en cascade est le comportement par défaut pour les relations requises comme indiqué, il aurait donc été défini automatiquement par EF Core si vous utilisiez d'abord le code et le flux de travail de migrations. Cependant, les concepteurs de votre base de données existante ont décidé de ne pas l'activer, vous devez donc soit vivre avec cela (et supprimer OnDelete(Cascade) car il doit refléter l'état de la base de données), soit le modifier en utilisant les méthodes susmentionnées. Quoi que vous décidiez, vous devez garder les métadonnées EF Core et la structure de la base de données physique synchronisées - à quelques exceptions près, de nombreux comportements EF Core reposent sur la base de données, il s'agit donc d'un effort de coopération.

Pour plus d'informations, consultez la section Suppression en cascade de la section Documentation officielle EF Core

2
Ivan Stoev 2 sept. 2020 à 07:20

J'utilise également OnDelete (DeleteBehavior.Cascade) pour tout supprimer. Avez-vous utilisé Include (x => x.BlogPost)?

var blogUser = context.BlogUser.Where(x => x.BlogUserId == userId).Include(x => x.BlogPost).FirstOrDefault();
context.BlogUser.Remove(blogUser);
context.SaveChanges();

Mais si le vôtre ne peut pas fonctionner, vous pouvez essayer cette méthode alternative. Bien qu'il ait 3 lignes, au moins cela fonctionne.

var blogPosts = context.BlogPost.Where(x => x.BlogUserId == blogUser.BlogUserId).ToList();
context.BlogPost.RemoveRange(blogPosts);
context.BlogUser.Remove(blogUser);
context.SaveChanges();
0
Asherguru 2 sept. 2020 à 07:22

Lors de la configuration de votre modèle, décidez simplement si vous devez ou non supprimer Cascade, par exemple:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<Book>()
        .HasOptional<Author>(b => b.Author)
        .WithMany()
        .WillCascadeOnDelete(false);
}
0
Roman Ryzhiy 2 sept. 2020 à 07:04