2012-05-31 20 views
13

Para una aplicación utilizando código de Primera EF 5 beta que tienen:¿Puede EF eliminar automáticamente los datos que quedan huérfanos, donde no se elimina el padre?

public class ParentObject 
{ 
    public int Id {get; set;} 
    public virtual List<ChildObject> ChildObjects {get; set;} 
    //Other members 
} 

y

public class ChildObject 
{ 
    public int Id {get; set;} 
    public int ParentObjectId {get; set;} 
    //Other members 
} 

Las operaciones CRUD relevantes son realizadas por los repositorios, cuando sea necesario.

En

OnModelCreating(DbModelBuilder modelBuilder) 

los he establecido:

modelBuilder.Entity<ParentObject>().HasMany(p => p.ChildObjects) 
      .WithOptional() 
      .HasForeignKey(c => c.ParentObjectId) 
      .WillCascadeOnDelete(); 

lo tanto, si se elimina un ParentObject, sus ChildObjects también lo son.

Sin embargo, si funciono:

parentObject.ChildObjects.Clear(); 
_parentObjectRepository.SaveChanges(); //this repository uses the context 

tengo la excepción:

pudo realizar la operación: La relación no podía ser cambiado debido a una o más de las propiedades de clave externa no es -nullible Cuando se realiza un cambio en una relación, la propiedad de clave foránea relacionada se establece en un valor nulo. Si la clave externa no admite valores nulos, se debe definir una nueva relación, se debe asignar a la propiedad de clave externa otro valor no nulo o se debe eliminar el objeto no relacionado.

Esto tiene sentido ya que la definición de las entidades incluye la restricción de la clave externa que se está rompiendo.

¿Puedo configurar la entidad para que se "limpie" cuando quede huérfana o debo eliminar manualmente estos ChildObject del contexto (en este caso usando un RepositorioDeObjetos).

+0

Afortunadamente, el equipo de EF [sabe de esto] (http://blog.oneunicorn.com/2012/06/02/deleting-orphans-with-entity-framework/) y es probable que llegar a una solución incorporada que no requiere modificar las partes internas – PinnyM

Respuesta

25

En realidad, es compatible, pero sólo cuando se utiliza Identifying relation. También funciona con código primero. Sólo tiene que definir la clave compleja para su ChildObject que contiene tanto Id y ParentObjectId:

modelBuilder.Entity<ChildObject>() 
      .HasKey(c => new {c.Id, c.ParentObjectId}); 

Debido a que la definición de dicha clave eliminará convención predeterminado de auto incrementa Id debe volver a definir de forma manual:

modelBuilder.Entity<ChildObject>() 
      .Property(c => c.Id) 
      .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); 

Ahora llamando parentObject.ChildObjects.Clear() elimina objetos dependientes.

Btw.el mapeo relación debe usar WithRequired para seguir sus clases reales porque si FK no es anulable, no es opcional:

modelBuilder.Entity<ParentObject>().HasMany(p => p.ChildObjects) 
      .WithRequired() 
      .HasForeignKey(c => c.ParentObjectId) 
      .WillCascadeOnDelete(); 
+0

Gran respuesta. Parece que tengo mucho que aprender sobre EF. – StuperUser

+1

@Ladislav Mmka ¿qué pasa con el caso unidireccional? Cuando ChildObject no sabe sobre el padre? – Davita

+0

@Ladislav Estoy usando EF6 e intenté hacer lo que hizo en los ejemplos. Sin embargo, al llamar a Collection.Clear() y AddOrUpdate (principal) no parece funcionar. ¿Hay algo que tenga que hacer en el contexto? – Jamez

-1

EF no lo admite de forma automática en este momento. Puede hacerlo anulando SaveChanges en su contexto y eliminando manualmente un objeto secundario que ya no tiene un elemento primario. El código sería algo como esto:

public override int SaveChanges() 
{ 
    foreach (var bar in Bars.Local.ToList()) 
    { 
     if (bar.Foo == null) 
     { 
      Bars.Remove(bar); 
     } 
    } 

    return base.SaveChanges(); 
} 
+3

Buena idea, excepto que esto debería darle un rendimiento terrible –

+3

¿Este código no se ejecutará para cada SaveChanges()? – Elisabeth

4

Para resolver este problema sin necesidad de crear una clave compleja, puede anular la SaveChanges de su DbContext, pero luego use ChangeTracker para evitar el acceso a la base de datos con el fin de encontrar objetos huérfanos.

Primera añadir un alojamiento de navegación a la ChildObject (se puede mantener int ParentObjectId propiedad si lo desea, funciona en ambos sentidos):

public class ParentObject 
{ 
    public int Id { get; set; } 
    public virtual List<ChildObject> ChildObjects { get; set; } 
} 

public class ChildObject 
{ 
    public int Id { get; set; } 
    public virtual ParentObject ParentObject { get; set; } 
} 

A continuación, busque los objetos huérfanos utilizando ChangeTracker:

public class MyContext : DbContext 
{ 
    //... 
    public override int SaveChanges() 
    { 
     HandleOrphans(); 
     return base.SaveChanges(); 
    } 

    private void HandleOrphans() 
    { 
     var orphanedEntities = 
      ChangeTracker.Entries() 
      .Where(x => x.Entity.GetType().BaseType == typeof(ChildObject)) 
      .Select(x => ((ChildObject)x.Entity)) 
      .Where(x => x.ParentObject == null) 
      .ToList(); 

     Set<ChildObject>().RemoveRange(orphanedEntities); 
    } 
} 

Su configuración se convierte en:

modelBuilder.Entity<ParentObject>().HasMany(p => p.ChildObjects) 
      .WithRequired(c => c.ParentObject) 
      .WillCascadeOnDelete(); 

Hice una prueba de velocidad simple iterando 10.000 veces. Con HandleOrphans() habilitado, tardó 1: 01.443 minutos en completarse, con el valor desactivado fue 0: 59.326 min (ambos son un promedio de tres ejecuciones). Código de prueba a continuación.

using (var context = new MyContext()) 
{ 
    var parentObject = context.ParentObject.Find(1); 
    parentObject.ChildObjects.Add(new ChildObject()); 
    context.SaveChanges(); 
} 

using (var context = new MyContext()) 
{ 
    var parentObject = context.ParentObject.Find(1); 
    parentObject.ChildObjects.Clear(); 
    context.SaveChanges(); 
} 
Cuestiones relacionadas