2011-12-22 9 views
10

共有主キーを使用するときに、EFを使用したTPTアーキテクチャでは、必要なON DELETE CASCADEが作成されません... EFコンテキストがサブクラス化されたテーブルの適切な削除順序を処理するとも言われていました(ただし、制約が破られているというエラーが発生し、サブデバスにON DELETE CASCADEを追加することで修正できます)クラステーブル)...EF 4.2でのTPT(Table Per Type)の問題と親オブジェクトの削除

より多くの背景情報...

私は数、タイトル、ページのリストを持っているセクションのクラスを、持っています。このページは、基本的なページプロパティを保持するスーパークラスを使用して設計されています。私は約10 +ページクラスのサブクラスを持っています。 SectionクラスはこれらのページのICollectionを保持します。 DBはサブクラス化されたテーブルでON DELETE CASCADEを除いて正しく作成されます。

私のコードはエンティティを作成し、DBに追加します。私はセクション(または全区間)を削除しようとした場合しかし、それは私のサブクラスのページテーブルのFK制約によるtodelete ...

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 ...(スキーマを "" Oracle用の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; 
} 

...他のモデル化するために、他のコードテーブル...

アプリはコンテンツを取り込め、関係は適切に設定されています。しかし、コースを削除しようとすると、PageContentテーブルのChapterPageの制約により、削除が失敗したというエラーが発生します。

ここでは、コースを削除するコードです(実際にすべてのコースを削除します)。 ..

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

私はPageContentとの共有のための主要ChapterPageとSectionPage表に 'ONは、DELETE CASCADEを' を追加した場合、削除が通過します。私が見てきた要約

唯一の解決策は、手動でONが私のサブクラスのページのすべてのテーブルのDELETE CASCADEを追加するために制約を変更することです。 DBを作成またはインスタンス化するためにEFを使用しないため、必要なEFテーブル用のDBスクリプトを生成するコード(DB全体の小さなサブセット)があるため、変更を実装できます(マイグレーションを適切にサポートしていないためまだ...)。

私は何かをミスコードしたり、モデルビルダーロジックの設定を忘れてしまったことを心から願っています。もしそうでなければ、EFの設計者は、実際の世界の状況ではハック回避策なしでは使用できないアーキテクチャ(TPT設計アプローチ)を定義しています。それは半完成の解決策です。私が間違ってはいけない、私はやったことが好きで、ほとんどのMSFTソリューションのように、最も基本的なアプリケーションの70%を使用しています。もっと複雑な状況には対応できません。

私は、DB設計をすべてEF流暢なAPI内に保持し、自己完結型であるようにしていました。それは私のためにそこに約98%、ちょうど彼らが仕事を終えた場合、多分次のリリースでいいだろう。少なくともCRUDの操作はすべて保存されます。

チャオ! ジム・ショー

+0

カスケード 'Course.Chapters'、' Chapter.Sections'と 'Section.Pages'のために削除して、これらの1対多の関係が必要とされてありますかオプション?私の場合、これは、BasePagesをコンテキストにロードして明示的に削除する必要があるかのように見え、EFは2つのDELETEステートメントを作成します(ベースおよび派生テーブル用)。削除が他のエンティティの一連のカスケード削除に依存する場合、DBはEFが明らかに作成しない適切なカスケード削除を伴うすべての関連オブジェクトを削除する責任があります。私はこれをバグと呼んでいますか、少なくとも何らかの隠された制限を知っていなければなりません。 – Slauma

+0

はいあります。私は、コンテキストを含むページのロジックを使用しようとしましたが、それでも失敗します。私は増分ではないので(EFの動的DB作成ロジックを使用することはできません)、Migrationプロジェクトが終了したときに、Add constraintsソリューションを使用することに決めました。私は、コンテキストを使用してDBスクリプトを生成するためのスクリプトジェネレータユーティリティを作成しています。別の.sqlファイルを使用してDBをモデル化します。 –

答えて

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の制約のために削除(またはSeedメソッドに適切なSQLコマンドを入れます)。
  • TPT継承に関連するエンティティをメモリにロードします。私のサンプルコードでは: - ベーステーブル用と派生テーブルに1つのエンティティは、コンテキストにロードされている

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

    、EFは、実際には2つのDELETE文を作成します。あなたのケースでは、TPTエンティティを取得する前にはるかに複雑なオブジェクトグラフをロードする必要があったため、これはひどい解決策です。

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

    コード例は、例外をスローする代わりから行を削除しないであろう。ParentICollection<DerivedPage>を有する(逆Parentプロパティは、次にDerivedPageにある)場合

さらに問題です派生テーブルだがではないベーステーブルから、BasePageが抽象であるため、エンティティを表すことができないファントム行が残っています。この問題は、カスケード削除によって解消できませんが、データベース内のそのようなナンセンスを避けるために、親を削除する前に、実際にコレクションをコンテキストにロードする必要がありました。

同様の質問と分析はここにあった:http://social.msdn.microsoft.com/Forums/en-US/adodotnetentityframework/thread/3c27d761-4d0a-4704-85f3-8566fa37d14e/

+0

情報をありがとう。私は、db作成のためのスクリプトとauto/hand-cranked制約スクリプトを使用することに決めました。この方法で私はまだアプリケーションでCRUD操作にEFを使うことができます(そして、それらはむしろ簡単です、コースを削除する、章を削除する...、DBを適切に扱います) –

+0

これはトリガーで解決できます。そのような機能に関する大きな警告。 – John

関連する問題