EntityFramewok: Cómo configurar Cascade-Delete para anular Claves Foráneas


La documentación de EntityFramework indica que el siguiente comportamiento es posible:

Si una clave foránea en la entidad dependiente es nullable, el código Primero lo hace no establecer cascade delete en la relación, y cuando el principal es eliminada la clave foránea se establecerá en null.

(de http://msdn.microsoft.com/en-us/jj591620 )

Sin embargo, no puedo lograr tal comportamiento.

Tengo las siguientes Entidades definidas con código-primero:

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

Aquí está la configuración de asignación de 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 clave foránea es nullable, la propiedad de navegación se asigna como Opcional, por lo que espero que la eliminación en cascada funcione como se describe como MSDN, es decir, para anular los MasterID de todos los hijos y luego eliminar el objeto Maestro.

Pero cuando realmente intento eliminar, obtengo el error de violación FK:

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

En SaveChanges() lanza lo siguiente:

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.

¿Estoy haciendo algo mal o lo hice malinterpretado lo que dice el MSDN?

Author: Alex, 2013-03-05

2 answers

Funciona de hecho como se describe, pero el artículo sobre MSDN no enfatiza que solo funciona si los hijos se cargan en el contexto también, no solo la entidad padre. Por lo tanto, en lugar de usar Find (que solo carga el padre) debe usar eager loading con Include (o cualquier otra forma de cargar los hijos en el contexto):

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

Esto eliminará el maestro de la base de datos, establecerá todas las claves foráneas en las entidades Child a null y escribirá instrucciones de ACTUALIZACIÓN para niños a la base de datos.

 43
Author: Slauma,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-03-05 17:48:34

Después de seguir la gran respuesta de @Slauma, todavía estaba recibiendo el mismo error que OP.

Así que no sea tan ingenuo como yo y piense que los ejemplos a continuación terminarán con el mismo resultado.

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;

Primero cargue los hijos en contexto antes de establecer entity state en deleted (si lo está haciendo de esa manera).

 0
Author: Quinton Smith,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-29 13:00:34