Search code examples
asp.net-mvcentity-frameworkasp.net-coreforeign-keysef-code-first

Introducing foreign key constraint may cause cycles or multiple cascade paths - How do I specify "ON DELETE NO ACTION"?


I have 6 entities in total but my issue is with these following 3: Departement, Filier and Etudiant.

The Departement entity contains many Filiere and many Etudiant entities, while Filiere and Etudiant are only associate with 1 Departement each.

This is my error message after adding migration and trying to update the database for the first time:

Introducing FOREIGN KEY constraint 'FK_Etudiant_Filiere_FiliereId' on table 'Etudiant' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Could not create constraint or index. See previous errors.

This is my Departement entity:

using System.ComponentModel.DataAnnotations;

namespace Scolarité.Models
{
    public class Departement
    {
        public int Id { get; set; }

        [Required(ErrorMessage = "Libellé Obligatoire")]
        [StringLength(100, MinimumLength = 3)]
        [Display(Name = "Departement")]
        public string LibelleD { get; set; }

        [Required(ErrorMessage = "Chef Departement Obligatoire")]
        [StringLength(30, MinimumLength = 3)]
        [Display(Name = " Chef Departement")]
        public string ChefD { get; set; }

        public virtual ICollection<Filiere> Filieres { get; set; }
        public virtual ICollection<Semestre> Semestre { get; set; }
        public virtual ICollection<Unite> Unite { get; set; }
        public virtual ICollection<Etudiant> Etudiant { get; set; }
    }
}

This is my Filiere entity:

using System.ComponentModel.DataAnnotations;

namespace Scolarité.Models
{
    public class Filiere
    {
        public int Id { get; set; }

        [Required(ErrorMessage = "Libellé filiére Obligatoire")]
        [StringLength(100, MinimumLength = 3)]
        [Display(Name = " Libelle Filiére")]
        public string LibelleF { get; set; }

        public int DepartementId { get; set; }
        public virtual Departement Departement { get; set; }
    
        public virtual ICollection<Etudiant> Etudiants { get; set; }
        public virtual ICollection<Unite> Unite { get; set; }
        public virtual ICollection<Semestre> Semestre { get; set; }
    }
}

This is my Etudiant entity:

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Scolarité.Models
{
    public class Etudiant
    {
        public int Id { get; set; }

        [Required(ErrorMessage = "Nom Obligatoire")]
        [StringLength(30, MinimumLength = 3)]
        public string Nom { get; set; }

        [Required(ErrorMessage = "Prenom Obligatoire")]
        [StringLength(30, MinimumLength = 3)]
        public string Prenom { get; set; }

        [Required(ErrorMessage = "CIN Obligatoire")]
        [RegularExpression(@"^\d{8}$", ErrorMessage = "Le CIN doit comporter 8 chiffres.")]
        public string Cin { get; set; }

        [Required(ErrorMessage = "Mot de passe Obligatoire")]
        [DataType(DataType.Password)]
        public string Password { get; set; }

        [Display(Name = " Departement")]
        public int DepartementId { get; set; }
        public virtual Departement Departement { get; set; }

        [Display(Name = " Filiére")]
        public int FiliereId { get; set; }
        public virtual Filiere Filiere { get; set; }
    }
}

I checked youtube videos and other posts relating to my problem on stackoverflow and tried their solutions but they didn't work for me, although I believe I'm most likely implementing them incorrectly, and that's why I'm here now.

  1. I tried making the foreign keys nullable even though they shouldn't be for my case, but that didn't solve the problem

  2. I tried changing the onDelete option to NoAction and to ClientSetNull by going to my DbContext and adding the following code to the OnModelCreating(ModelBuilder modelBuilder) method, but it still throws the same error message as if though I didn't change a thing:

OnModelCreating method for my DbContext:

using Microsoft.EntityFrameworkCore;
using Scolarité.Models;

public class ScolaritéContext : DbContext
{
    public ScolaritéContext(DbContextOptions<ScolaritéContext> options)
        : base(options)
    {
    }

    public DbSet<Departement> Departement { get; set; } = default!;
    public DbSet<EE> EE { get; set; } = default!;
    public DbSet<Etudiant> Etudiant { get; set; } = default!;
    public DbSet<Filiere> Filiere { get; set; } = default!;
    public DbSet<Semestre> Semestre { get; set; } = default!;
    public DbSet<Unite> Unite { get; set; } = default!;


    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<Departement>()
                .HasMany(d => d.Filieres)
                .WithOne(f => f.Departement)
                .HasForeignKey(f => f.DepartementId)
                .OnDelete(DeleteBehavior.NoAction);
        
        modelBuilder.Entity<Filiere>()
           .HasMany(f => f.Etudiants)
           .WithOne(e => e.Filiere)
           .HasForeignKey(e => e.FiliereId)
           .OnDelete(DeleteBehavior.NoAction); 

        modelBuilder.Entity<Etudiant>()
                .HasOne(e => e.Departement)
                .WithMany(d => d.Etudiant)
                .HasForeignKey(e => e.DepartementId)
                .OnDelete(DeleteBehavior.NoAction);

        modelBuilder.Entity<Etudiant>()
                .HasOne(e => e.Filiere)
                .WithMany(f => f.Etudiants)
                .HasForeignKey(e => e.FiliereId)
                .OnDelete(DeleteBehavior.NoAction);
    }
}

Solution

  • Without using .OnDelete(DeleteBehavior.NoAction);, adding following code to modelbuilder solved my problem:

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
    ...
        if (modelBuilder == null)
            throw new ArgumentNullException("modelBuilder");
    
        // for the other conventions, we do a metadata model loop
        foreach (var entityType in modelBuilder.Model.GetEntityTypes())
        {
            // equivalent of modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
            entityType.SetTableName(entityType.DisplayName());
    
            // equivalent of modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
            entityType.GetForeignKeys()
                .Where(fk => !fk.IsOwnership && fk.DeleteBehavior == DeleteBehavior.Cascade)
                .ToList()
                .ForEach(fk => fk.DeleteBehavior = DeleteBehavior.Restrict);
        }
    
        base.OnModelCreating(modelBuilder);
    ...
    }