web-dev-qa-db-fra.com

Entity Framework 6: Code First Cascade delete

Il y a donc plusieurs questions similaires à ce sujet, mais j'ai toujours des problèmes pour déterminer exactement ce qui me manque dans mon scénario simplifié.

Disons que j'ai les tableaux suivants, qui portent bien mon nom:

'JohnsParentTable' (Id, Description) 
'JohnsChildTable' (Id, JohnsParentTableId, Description)

Avec les classes qui en résultent

public class JohnsParentTable
{
    public int Id { get; set; }
    public string Description { get; set; }
    public virtual ICollection<JohnsChildTable> JohnsChildTable { get; set; }

    public JohnsParentTable()
    {
        JohnsChildTable = new List<JohnsChildTable>();
    }
}

internal class JohnsParentTableConfiguration : EntityTypeConfiguration<JohnsParentTable>
{
    public JohnsParentTableConfiguration()
    {
        ToTable("dbo.JohnsParentTable");
        HasKey(x => x.Id);
        Property(x => x.Id).HasColumnName("Id").IsRequired().HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
        Property(x => x.Description).HasColumnName("Description").IsRequired().HasMaxLength(50);
    }
}

public class JohnsChildTable
{
    public int Id { get; set; }
    public string Description { get; set; }
    public int JohnsParentTableId { get; set; }
    public JohnsParentTable JohnsParentTable { get; set; }
}

internal class JohnsChildTableConfiguration : EntityTypeConfiguration<JohnsChildTable>
{
    public JohnsChildTableConfiguration()
    {
        ToTable("dbo.JohnsChildTable");
        HasKey(x => x.Id);
        Property(x => x.Id).HasColumnName("Id").IsRequired().HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
        Property(x => x.Description).HasColumnName("Description").IsRequired().HasMaxLength(50);
        HasRequired(a => a.JohnsParentTable).WithMany(c => c.JohnsChildTable).HasForeignKey(a => a.JohnsParentTableId);
    }
}

Dans la base de données, j'ai une ligne dans la table parent avec un ID de 1 ainsi que deux lignes dans la table enfant liées à ce parent. Si je fais ça:

var parent = db.JohnsParentTable.FirstOrDefault(a => a.Id == 1)

L'objet est correctement rempli. Cependant, si j'essaie de supprimer cette ligne:

var parent = new Data.Models.JohnsParentTable() { Id = 1 };
db.JohnsParentTable.Attach(parent);
db.JohnsParentTable.Remove(parent);

db.SaveChanges();

Le framework d'entité essaie d'exécuter ce qui suit:

DELETE [dbo].[JohnsParentTable]
WHERE ([Id] = @0)
-- @0: '1' (Type = Int32)
-- Executing at 1/23/2014 10:34:01 AM -06:00
-- Failed in 103 ms with error: The DELETE statement conflicted with the REFERENCE constraint "FK_JohnsChildTable_JohnsParentTable". The conflict occurred in database "mydatabase", table "dbo.JohnsChildTable", column 'JohnsParentTableId'.
The statement has been terminated.

Ma question est alors de savoir ce qui me manque exactement pour m'assurer qu'Entity Framework sait qu'il doit supprimer les lignes "JohnsChildTable" avant de supprimer le parent?

14
John

Cela dépend si vous voulez qu'Entity Framework supprime les enfants ou si vous voulez que la base de données s'en occupe.

Si vous souhaitez qu'EF génère une instruction delete pour tous les enfants et les exécute avant de supprimer le parent, vous devez d'abord charger tous les enfants en mémoire.

Vous ne pouvez donc pas simplement créer une entité "factice" avec seulement la clé remplie et vous attendre à ce que les enfants soient supprimés.

Pour que cela fonctionne, vous devez laisser la base de données gérer la suppression.

La clé étrangère sur la table enfant devrait cependant avoir des suppressions en cascade activées. Si tel est le cas, Entity Framework crée simplement une instruction delete pour le parent, et la base de données sait également supprimer les enfants.

Entity Framework crée une clé étrangère avec des suppressions en cascade activées par défaut, si la relation est requise, comme dans votre cas. (La clé étrangère ne peut pas être nulle).

Si vous avez créé la base de données vous-même, vous devez vous souvenir de l'activer.

12
Peter Hansen

Je pense qu'il vaut mieux remplacer la méthode OnModelCreating et ajouter ce code.

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
   modelBuilder.Entity<JohnsChildTable>()
               .HasRequired(t=>t.JohnsParentTable)
               .WithMany(t=>t.JohnsChildTables)
               .HasForeignKey(d=>d.JohnsParentTableId)
               .WillCascadeOnDelete(true);

            base.OnModelCreating(modelBuilder);
}

J'ai mis à true WillCascadeOnDelete (true)

17