Entity framework 在EF 4.2中使用TPT(每种类型的表)和删除父对象时出现问题

Entity framework 在EF 4.2中使用TPT(每种类型的表)和删除父对象时出现问题,entity-framework,Entity Framework,据我在几篇文章中所了解,TPT architecure和EF在使用共享主键时不会创建必要的on DELETE级联。。。。还有人说EF上下文将处理子类表的正确删除顺序(但是我确实收到一个错误,它打破了约束,我可以通过在子类表上添加ON DELETE级联来修复它) 更多背景信息 我有一个Section类,它有编号、标题和页面列表。该页面使用包含基本页面属性的超类进行设计。我有大约10+个page类的子类。Section类保存这些页面的ICollection。数据库是正确创建的,但子类表上的no ON

据我在几篇文章中所了解,TPT architecure和EF在使用共享主键时不会创建必要的on DELETE级联。。。。还有人说EF上下文将处理子类表的正确删除顺序(但是我确实收到一个错误,它打破了约束,我可以通过在子类表上添加ON DELETE级联来修复它)

更多背景信息

我有一个Section类,它有编号、标题和页面列表。该页面使用包含基本页面属性的超类进行设计。我有大约10+个page类的子类。Section类保存这些页面的ICollection。数据库是正确创建的,但子类表上的no ON DELETE CASCADE除外

我的代码将创建实体并添加到数据库中。但是,如果我试图删除一个节(或所有节),由于子类页表上的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; }
}
如果我在ChapterPage和SectionPage表中为其与PageContent共享的主目录添加“ON DELETE CASCADE”,则删除操作将完成

总之

我看到的唯一解决方案是手动更改约束,为我的所有子类页表添加ON-DELETE级联。我可以实现更改,因为我有代码为我需要的EF表(整个DB的一小部分)生成DB脚本,因为我们不会使用EF创建或实例化DB(因为它还不支持迁移…)

我真诚地希望我有一些代码错误,或者忘记了模型生成器逻辑中的一些设置。因为如果不是的话,EF设计师已经定义了一种架构(TPT设计方法),如果没有一个黑客解决方案,它就不能在任何现实世界中使用。这是一个半成品的解决方案。请不要误解我的意思,我喜欢已经完成的工作,也喜欢大多数MSFT解决方案,它可以满足70%的最基本应用程序使用。它只是没有为更复杂的情况做好准备

我试图将DB设计保持在EF fluent API和自包含的范围内。这对我来说是98%,如果他们能完成这项工作就好了,也许在下一个版本中。至少它帮我省去了所有的积垢操作

再见!
吉姆·肖(Jim Shaw)

我用一个简单一点的例子再现了这个问题:

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
            }
        }
    }
}
当实体加载到上下文中时,EF实际上创建了两个DELETE语句——一个用于基表,另一个用于派生表。在您的例子中,这是一个糟糕的解决方案,因为在获取TPT实体之前,您必须加载更复杂的对象图

如果
Parent
具有
ICollection
(并且反向
Parent
属性位于
DerivedPage
中,则问题更大):

公共类父类
{
公共int Id{get;set;}
公共字符串名称{get;set;}
公共ICollection页{get;set;}
}
公共抽象类基页
{
公共int Id{get;set;}
公共字符串名称{get;set;}
}
公共类DerivedPage:BasePage
{
公共字符串DerivedName{get;set;}
公共父级{get;set;}
}
示例代码不会引发异常,而是从派生表中删除行,但从基表中删除而不是,留下一个无法再表示实体的幻影行,因为
BasePage
是抽象的。通过级联删除无法解决此问题,但实际上,您必须先将集合加载到上下文中,然后才能删除父集合,以避免数据库中出现这种无意义的情况


这里有一个类似的问题和分析:

是否存在对
课程.章节
章节.章节
章节.页面
的级联删除,这些一对多关系是必需的还是可选的?对我来说,这看起来好像必须将BasePages加载到上下文中,然后明确地删除它们,然后EF创建两个delete语句(对于base和派生表)。如果删除依赖于其他实体的级联删除链,则DB负责使用EF显然不创建的适当级联删除来删除每个相关对象。我认为这是一个bug,或者至少是一种我们必须意识到的隐藏的限制。是的。我试图让上下文使用Include pages逻辑,但仍然失败。我决定使用添加约束解决方案,因为我们将/不能使用EF dynamic DB创建逻辑,因为它不是增量的(可能在迁移项目完成时)。我一直在编写一个脚本生成器实用程序来使用上下文写db脚本和一些自动生成的级联删除脚本来扩充db脚本)。我们将使用一个单独的.sql文件对数据库进行建模…感谢您提供的信息。我决定使用脚本和自动/手动启动的约束脚本来创建数据库。这样,我仍然可以在我们的应用程序中使用EF进行CRUD操作(它们非常简单,删除课程,删除章节…并适当地进行DB处理。虽然它可以通过触发器解决,但实体框架应该处理这一问题,或者对这类功能发出警告。
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;
}
using (MyContext ctx = new MyContext())
{
    ctx.Courses.ToList().ForEach(crs => ctx.Courses.Remove(crs));
    AttachLookupEntities(ctx);
    ctx.SaveChanges();
}
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
            }
        }
    }
}
var parent = ctx.Parents.Include(p => p.Pages).FirstOrDefault();
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; }
}