2011-12-22 7 views
10

Por lo que entiendo en varias publicaciones, el TFT architecure, con EF, no crea el CASCADE EN ELIMINAR necesario al usar una clave primaria compartida ... También se dijo que el contexto EF manejará el orden correcto de eliminación de las tablas sub-clasificadas (sin embargo, obtengo un error que rompe la restricción y que puedo solucionarlo añadiendo ON DELETE CASCADE en el sub- tabla de clase) ...Problemas al utilizar TPT (tabla por tipo) en EF 4.2 y eliminación de objetos principales

más información de fondo ...

tengo una clase de sección, que tiene un número, título y una lista de páginas. La página está diseñada usando una súper clase que contiene propiedades básicas de la página. Tengo aproximadamente 10+ subclases de la clase de página. La clase Section contiene una colección de estas páginas. El DB se crea correctamente con la excepción de no ON DELETE CASCADE en las tablas sub-clasificadas.

Mi código creará las entidades y las agregará a la multa de DB. Sin embargo, si intento eliminar una sección (o todas las secciones) se produce un error todelete debido a la restricción FK en mi tabla de páginas subclase ...

public abstract BaseContent 
{ 
... common properties which are Ignored in the DB ... 
} 

public class Course : BaseContent 
{ 
    public int Id {get;set;} 
    public string Name {get;set;} 
    public string Descripiton {get;set;} 
    public virtual ICollection<Chapter> Chapters{get;set;} 
    ... 
} 

public class Chapter : BaseContent 
{ 
    public int Id {get;set;} 
    public int Number {get;set;} 
    public string Title {get;set;} 
    public virtual Course MyCourse{get;set;} 
    public virtual ICollection<Section> Sections{get;set;} 
    ... 
} 

public class Section : BaseContent 
{ 
    public int Id {get;set;} 
    public int Number {get;set;} 
    public string Title {get;set;} 
    public virtual Chapter MyChapter {get;set;} 
    public virtual ICollection<BasePage> Pages {get;set;} 
    ... 
} 

public abstract class BasePage : BaseContent, IComparable 
{ 
    public int Id { get; set; } 
    public string Title { get; set; } 
    public string PageImageRef { get; set; } 
    public ePageImageLocation ImageLocationOnPage { get; set; } 
    public int PageNumber { get; set; } 
    public virtual Section MySection { get; set; } 
    ... 
} 

public class ChapterPage : BasePage 
{ 
    public virtual int ChapterNumber { get; set; } 
    public virtual string ChapterTitle { get; set; } 
    public virtual string AudioRef { get; set; } 
} 

public class SectionPage : BasePage 
{ 
    public virtual int SectionNumber { get; set; } 
    public virtual string SectionTitle { get; set; } 
    public virtual string SectionIntroduction { get; set; } 
} 

... además de unos 8 otras subclases BasePage ...

public class MyContext: DbContext 
{ 
... 
    public DbSet<Course> Courses { get; set; } 
    public DbSet<Chapter> Chapters { get; set; } 
    public DbSet<Section> Sections { get; set; } 
    public DbSet<BasePage> Pages { get; set; } 
... 
} 

.. API Fluido ... (esquema nota se define en "" para SqlServer, Oracle para su el nombre de esquema)

private EntityTypeConfiguration<T> configureTablePerType<T>(string tableName) where T : BaseContent 
{ 
    var config = new EntityTypeConfiguration<T>(); 

    config.ToTable(tableName, Schema); 

    // This adds the appropriate Ignore calls on config for the base class BaseContent 
    DataAccessUtilityClass.IgnoreAllBaseContentProperties<T>(config); 

    return config; 
} 

public virtual EntityTypeConfiguration<BasePage> ConfigurePageContent() 
{ 
    var config = configureTablePerType<BasePage>("PageContent"); 

    config.HasKey(pg => pg.Id); 
    config.HasRequired(pg => pg.Title); 
    config.HasOptional(pg => pg.PageImageRef); 

    config.Ignore(pg => pg.ImageLocationOnPage); 

    return config; 
} 

public virtual EntityTypeConfiguration<ChapterPage> ConfigureChapterPage() 
{ 
    var config = configureTablePerType<ChapterPage>("ChapterPage"); 

    config.HasOptional(pg => pg.AudioRef); 
    config.Ignore(pg => pg.ChapterNumber); 
    config.Ignore(pg => pg.ChapterTitle); 

    return config; 
} 

public virtual EntityTypeConfiguration<SectionPage> ConfigureSectionPage() 
{ 
    var config = configureTablePerType<SectionPage>("SectionPage"); 

    config.HasOptional(pg => pg.AudioRef); 
    config.Ignore(pg => pg.SectionNumber); 
    config.Ignore(pg => pg.SectionTitle); 

    return config; 
} 

... otro código para modelar otra tablas ...

De modo que la aplicación puede llenar el contenido y las relaciones están configuradas correctamente. Sin embargo, cuando intento eliminar el curso, aparece el error de que la eliminación falló debido a la restricción en la tabla ChapterPage to PageContent.

Aquí está el código que borra el curso (en realidad elimino todos los cursos). ..

using (MyContext ctx = new MyContext()) 
{ 
    ctx.Courses.ToList().ForEach(crs => ctx.Courses.Remove(crs)); 
    AttachLookupEntities(ctx); 
    ctx.SaveChanges(); 
} 

Si añado el 'ON DELETE CASCADE' en la tabla ChapterPage y SectionPage por su principal compartida con PageContent, la eliminación pasa por.

En resumen,

La única solución que he visto es alterar manualmente las restricciones para añadir el ON DELETE CASCADE para todos mis tablas de páginas sub-clase. Puedo implementar el cambio, ya que tengo un código que genera el script DB para las tablas EF que necesito (un pequeño subconjunto de nuestro DB completo) ya que no usaremos EF para crear o crear instancias del DB (ya que no admite migraciones adecuadas todavía...).

Sinceramente espero haber codificado mal algo, u olvidé alguna configuración en la lógica del generador de modelos. Porque si no, los diseñadores de EF han definido un enfoque de diseño de arquitectura (TPT) que no se puede usar en ninguna situación del mundo real sin una solución de hack. Es una solución a medio terminar. No me malinterprete, me gusta el trabajo que se ha realizado y, como la mayoría de las soluciones de MSFT, funciona con el 70% de los usos de aplicaciones más básicos. Simplemente no está listo para situaciones más complejas.

Estaba tratando de mantener el diseño de la base de datos dentro de la API EF fluida y autónomo. Me espera un 98%, sería bueno si terminaran el trabajo, tal vez en el próximo lanzamiento. Al menos me ahorra todas las operaciones CRUD.

Ciao! Jim Shaw

+0

¿Hay eliminar una cascada para 'Course.Chapters',' 'Chapter.Sections' y Section.Pages' y son estas relaciones uno-a-muchos o requeridos ¿Opcional? Para mí esto parece como si uno tuviera que cargar las páginas base en el contexto y luego eliminarlas explícitamente y EF crea entonces dos declaraciones DELETE (para la tabla base y la tabla derivada). Si la eliminación se basa en una cadena de eliminaciones en cascada de otras entidades, el DB es responsable de eliminar todos los objetos relacionados con las eliminaciones en cascada adecuadas que aparentemente EF no crea. Llamaría a esto un error o al menos algún tipo de limitación oculta que uno debe conocer. – Slauma

+0

Sí, hay. Traté de que el contexto utilizara la lógica Incluir páginas, pero aún falla. Decidí ir con la solución Agregar restricciones, ya que usaremos/no podremos usar la lógica de creación de DB dinámica de EF ya que no es incremental (tal vez cuando finalice el proyecto de migraciones). He estado escribiendo una utilidad de generación de scripts para usar el script de escritura contextual db y algunos scripts de eliminación en cascada generados automáticamente para aumentar el script db). Vamos a utilizar un archivo .sql por separado para modelar el DB ... –

Respuesta

5

He reproducido el problema con el ejemplo un poco más simple:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Data.Entity; 

namespace EFTPT 
{ 
    public class Parent 
    { 
     public int Id { get; set; } 
     public string Name { get; set; } 
     public ICollection<BasePage> Pages { get; set; } 
    } 

    public abstract class BasePage 
    { 
     public int Id { get; set; } 
     public string Name { get; set; } 
     public Parent Parent { get; set; } 
    } 

    public class DerivedPage : BasePage 
    { 
     public string DerivedName { get; set; } 
    } 

    public class MyContext : DbContext 
    { 
     public DbSet<Parent> Parents { get; set; } 
     public DbSet<BasePage> BasePages { get; set; } 

     protected override void OnModelCreating(DbModelBuilder modelBuilder) 
     { 
      modelBuilder.Entity<Parent>() 
       .HasMany(p => p.Pages) 
       .WithRequired(p => p.Parent); // creates casc. delete in DB 

      modelBuilder.Entity<BasePage>() 
       .ToTable("BasePages"); 

      modelBuilder.Entity<DerivedPage>() 
       .ToTable("DerivedPages"); 
     } 
    } 

    class Program 
    { 
     static void Main(string[] args) 
     { 
      using (var ctx = new MyContext()) 
      { 
       var parent = new Parent { Pages = new List<BasePage>() }; 
       var derivedPage = new DerivedPage(); 

       parent.Pages.Add(derivedPage); 

       ctx.Parents.Add(parent); 
       ctx.SaveChanges(); 
      } 

      using (var ctx = new MyContext()) 
      { 
       var parent = ctx.Parents.FirstOrDefault(); 
       ctx.Parents.Remove(parent); 
       ctx.SaveChanges(); // exception here 
      } 
     } 
    } 
} 

Esto le da a la misma excepción que tenía también. Sólo las soluciones parecen ser:

  • De cualquier configuración de eliminación en cascada para la restricción de TPT en la base de datos de forma manual, como ya probadas (o poner una orden apropiada SQL en el método Seed).
  • O cargue las entidades que están involucradas en la herencia TPT en la memoria. En mi ejemplo de código:

    var parent = ctx.Parents.Include(p => p.Pages).FirstOrDefault(); 
    

    Cuando las entidades se cargan en el contexto, EF crea en realidad dos sentencias DELETE - una para la tabla base y una para la tabla derivada. En su caso, esta es una solución terrible porque tuvo que cargar un gráfico de objetos mucho más complejo antes de poder obtener las entidades TPT.

Aún más problemático es si Parent tiene un ICollection<DerivedPage> (y la propiedad Parent inversa está en DerivedPage a continuación):

public class Parent 
{ 
    public int Id { get; set; } 
    public string Name { get; set; } 
    public ICollection<DerivedPage> Pages { get; set; } 
} 

public abstract class BasePage 
{ 
    public int Id { get; set; } 
    public string Name { get; set; } 
} 

public class DerivedPage : BasePage 
{ 
    public string DerivedName { get; set; } 
    public Parent Parent { get; set; } 
} 

El código de ejemplo no sería una excepción pero en lugar de eliminar la fila la tabla derivada pero no de la tabla base, dejando una fila fantasma que ya no puede representar a una entidad porque BasePage es abstracto. Este problema no se puede resolver mediante una eliminación en cascada, pero en realidad se vio obligado a cargar la colección en el contexto antes de que pueda eliminar el elemento primario para evitar esas tonterías en la base de datos.

Una pregunta y análisis similar fue aquí: http://social.msdn.microsoft.com/Forums/en-US/adodotnetentityframework/thread/3c27d761-4d0a-4704-85f3-8566fa37d14e/

+0

Gracias por la información. Decidí usar las secuencias de comandos y la secuencia de comandos auto/manual para la creación de db. De esta manera, todavía puedo usar EF para operaciones CRUD en nuestra aplicación (y son bastante simples, eliminan el curso, eliminan el capítulo ... y maneja DB apropiadamente. –

+0

Sin embargo, es solucionable con un desencadenador y realmente el marco de entidad debe manejar esto o poner una gran advertencia sobre tales características. – John

Cuestiones relacionadas