2011-12-22 36 views
10

根据我对几篇文章的理解,使用共享主键时,使用EF的TPT架构不会创建必需的ON DELETE CASCADE ... 。也有人说,EF上下文将处理删除子分类表的正确顺序(但是,我得到一个错误,它打破了约束,我可以通过在子分类表上添加ON DELETE CASCADE来修复它,类表)...在EF 4.2中使用TPT(Table Per Type)和删除父对象的问题

更多的背景信息...

我有一个科类,它有一个数字,标题和页面列表。该页面使用一个拥有基本页面属性的超类来设计。我有大约10个以上的页面类的子类。 Section类保存这些页面的ICollection。除了在子分类表上没有ON DELETE CASCADE之外,数据库已正确创建。

我的代码将创建实体并添加到数据库罚款。但是,如果我尝试删除部分(或全部路段)失败todelete由于我的子类的页表中的FK约束...

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

...加上约8其他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 ...(注模式被定义为 “” 为SQLSERVER,甲骨文公司的架构名称)

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

...其他代码到其他模型表...

因此,该应用程序能够填充内容并正确设置关系。但是,当我尝试删除该课程时,出现由于ChapterPage到PageContent表的约束而导致删除失败的错误。

以下是删除课程(实际上我删除所有课程)的代码。 ..

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

如果我加入 'ON DELETE CASCADE' 在ChapterPage和SectionPage表与PageContent其共享主,删除经过。

综上所述,

唯一的解决办法,我所看到的是手动更改约束添加ON DELETE CASCADE对我所有的子类的页表。我可以实现更改,因为我有代码,它为我需要的EF表生成DB脚本(我们整个DB的一小部分),因为我们不会使用EF创建或实例化DB(因为它不能正确支持迁移至今......)。

我真诚地希望我错了某些东西,或者忘记了模型生成器逻辑中的一些设置。因为如果没有,EF设计人员已经定义了一种架构(TPT设计方法),在没有任何解决方法的情况下,这种架构不能用于任何现实世界。这是一个完成的解决方案。不要误解我的意思,我喜欢已完成的工作,并且像大多数MSFT解决方案一样,它适用于大多数基本应用程序的70%。它只是没有准备好更复杂的情况。

我试图保持数据库设计都在EF流利的API和独立的。这对我来说大约是98%,如果他们完成了这项工作,可能会在下一个版本中完成工作。至少它为我节省了所有的CRUD操作。

Ciao! 吉姆·肖

+0

有级联删除'Course.Chapters','Chapter.Sections'和'Section.Pages'和需要这些一个一对多的关系或可选的?对我来说,这看起来好像一个人必须将BasePages加载到上下文中,然后将其明确地删除,然后EF创建两个DELETE语句(对于基本表和派生表)。如果删除依赖于其他实体的级联删除链,DB负责通过正确的级联删除来删除每个相关对象,这些EF明显不会创建。我会把这称为一个错误或者至少是某种需要意识到的隐藏限制。 – Slauma 2011-12-22 12:27:45

+0

是的。我试图让上下文使用包含页面逻辑,但仍然失败。我决定采用Add constraints解决方案,因为我们将/不能使用EF动态数据库创建逻辑,因为它不是增量式的(可能在迁移项目完成时)。我一直在编写脚本生成器实用程序以使用上下文写入db脚本和一些自动生成的级联删除脚本来扩充db脚本)。我们将使用单独的.sql文件来建模数据库... – 2011-12-23 01:38:31

回答

5

我已复制带有一点点简单的例子问题:

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

这让你有太多相同的异常。唯一的解决办法似乎是:

  • 要么设置级联删除在DB的TPT约束手动,因为你已经测试(或把适当的SQL命令到Seed方法)。
  • 或将参与TPT继承的实体加载到内存中。在我的例子代码:

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

    当实体被加载到环境中,EF创建实际上是两个DELETE语句 - 一个用于基表和一个派生表。在你的情况下,这是一个可怕的解决方案,因为在获得TPT实体之前你必须加载更复杂的对象图。

更麻烦的是,如果ParentICollection<DerivedPage>(和逆Parent属性是DerivedPage则):

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

示例代码不会抛出异常,而是从删除的行从派生表中删除而不是,因此BasePage是抽象的,因此留下不能再代表实体的幻像行。这个问题不能通过级联删除来解决,但实际上你必须将集合加载到上下文中,然后才能删除父级以避免数据库中存在这样的废话。

类似的问题,并分析在这里:http://social.msdn.microsoft.com/Forums/en-US/adodotnetentityframework/thread/3c27d761-4d0a-4704-85f3-8566fa37d14e/

+0

感谢您的信息。我决定使用脚本和自动/手动约束脚本来创建数据库。这样我仍然可以在我们的应用程序中使用EF进行CRUD操作(并且它们相当简单,删除当然,删除章节...以及DB适当地处理。 – 2011-12-23 01:42:19

+0

虽然它可以通过触发器解决,但实际上实体框架应该能够处理这个问题,对这些功能有很大的警告。 – John 2013-12-11 16:19:32

相关问题