web-dev-qa-db-fra.com

EntityFramewok: Comment configurer Cascade-Delete pour annuler les clés étrangères

La documentation d'EntityFramework indique que le comportement suivant est possible:

Si une clé étrangère de l'entité dépendante est nullable, Code First le fait ne pas définir la suppression en cascade sur la relation et lorsque le principal est supprimé la clé étrangère sera définie sur null.

(from http://msdn.Microsoft.com/en-us/jj591620 )

Cependant, je ne peux pas obtenir un tel comportement.

J'ai les entités suivantes définies avec code-first:

public class TestMaster
{
    public int Id { get; set; }
    public string Name { get; set; }        
    public virtual ICollection<TestChild> Children { get; set; }       
}

public class TestChild
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual TestMaster Master { get; set; }
    public int? MasterId { get; set; }
}

Voici la configuration du mappage de l'API Fluent:

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<TestMaster>()
                    .HasMany(e => e.Children)
                    .WithOptional(p => p.Master).WillCascadeOnDelete(false);

        modelBuilder.Entity<TestChild>()
                    .HasOptional(e => e.Master)
                    .WithMany(e => e.Children)
                    .HasForeignKey(e => e.MasterId).WillCascadeOnDelete(false);
    }

La clé étrangère a la valeur nullable, la propriété de navigation est mappée en tant que facultatif. Par conséquent, la suppression en cascade doit fonctionner comme décrit en tant que MSDN, c’est-à-dire annuler les identifiants principaux de tous les enfants puis supprimer l’objet maître.

Mais lorsque j'essaie réellement de supprimer, j'obtiens l'erreur de violation FK:

 using (var dbContext = new TestContext())
        {
            var master = dbContext.Set<TestMaster>().Find(1);
            dbContext.Set<TestMaster>().Remove(master);
            dbContext.SaveChanges();
        }

Sur SaveChanges (), il jette les éléments suivants:

System.Data.Entity.Infrastructure.DbUpdateException : An error occurred while updating the entries. See the inner exception for details.
----> System.Data.UpdateException : An error occurred while updating the entries. See the inner exception for details.
----> System.Data.SqlClient.SqlException : The DELETE statement conflicted with the REFERENCE constraint "FK_dbo.TestChilds_dbo.TestMasters_MasterId". The conflict occurred in database "SCM_Test", table "dbo.TestChilds", column 'MasterId'.
The statement has been terminated.

Est-ce que je fais quelque chose de mal ou ai-je mal compris ce que dit le MSDN?

30
Alex

Cela fonctionne bien comme décrit, mais l’article sur MSDN manque de souligner que cela ne fonctionne que si les enfants sont chargés dans le contexte aussi, pas seulement l’entité parente. Ainsi, au lieu d'utiliser Find (qui ne charge que le parent), vous devez utiliser un chargement rapide avec Include (ou tout autre moyen de charger les enfants dans le contexte):

using (var dbContext = new TestContext())
{
    var master = dbContext.Set<TestMaster>().Include(m => m.Children)
        .SingleOrDefault(m => m.Id == 1);
    dbContext.Set<TestMaster>().Remove(master);
    dbContext.SaveChanges();
}

Cela supprimera le maître de la base de données, définira toutes les clés étrangères des entités Child sur null et écrira des instructions UPDATE pour les enfants dans la base de données.

44
Slauma

Après avoir suivi l'excellente réponse de @ Slauma, j'avais toujours la même erreur que OP.

Donc, ne soyez pas aussi naïf que moi et pensez que les exemples ci-dessous aboutiront au même résultat.

dbCtx.Entry(principal).State = EntityState.Deleted;
dbCtx.Dependant.Where(d => d.PrincipalId == principalId).Load();

// code above will give error and code below will work on dbCtx.SaveChanges()

dbCtx.Dependant.Where(d => d.PrincipalId == principalId).Load();
dbCtx.Entry(principal).State = EntityState.Deleted;

Chargez d'abord les enfants dans le contexte avant en définissant l'état de l'entité sur supprimé (si vous procédez ainsi.

0
Quinton Smith