2011-12-22 6 views
10

Z tego, co rozumiem w kilku wpisach, architektura TPT, z EF, nie tworzy koniecznego NA KASKU DELETE, gdy używasz współużytkowanego klucza głównego ... Zostało również powiedziane, że kontekst EF poradzi sobie z prawidłową kolejnością usuwania podklasowanych tabel (jednak dostaję błąd, że łamie to ograniczenie i mogę je naprawić dodając NA KASKU NA KASKU klasa tabela) ...Problemy z używaniem TPT (Tabela na typ) w EF 4.2 i usuwanie obiektów nadrzędnych

więcej informacji tło ...

mam klasy sekcji, która ma numer, tytuł i listę stron. Strona została zaprojektowana przy użyciu super klasy, która zawiera podstawowe właściwości strony. Mam około 10+ podklas klasy strony. Klasa Section posiada ICollection tych stron. DB jest tworzony poprawnie, z wyjątkiem braku ON DELETE CASCADE w podklasowanych tabelach.

Mój kod utworzy encje i doda do grzywny DB. Jednak, gdy próbuję usunąć sekcję (lub wszystkie sekcje) nie powiedzie todelete ze względu na ograniczenie FK na moim podklasy widoku tabeli ...

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

... plus około 8 innych BasePage podklasy ...

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

.. Fluent API ... (uwaga Schema jest zdefiniowana "" dla SqlServer, Oracle jego nazwa schematu)

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

... inny kod inny model stoły ...

Dzięki temu aplikacja może wypełniać treść, a relacje są poprawnie skonfigurowane. Jednak, gdy próbuję usunąć kurs, pojawia się błąd, że usunięcie nie powiodło się z powodu ograniczenia w tabeli ChapterPage do PageContent.

Oto kod, który usuwa Kurs (w rzeczywistości usuwam wszystkie kursy). ..

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

jeśli dodać 'ON DELETE CASCADE' w tabeli ChapterPage i SectionPage za wspólną podstawowej z PageContent, kasowania przechodzi.

Podsumowując,

Jedynym rozwiązaniem, które widziałem jest ręcznie zmieniać ograniczeń dodać ON DELETE CASCADE do wszystkich moich podklasa tablice stron. Mogę zaimplementować zmianę, ponieważ mam kod, który generuje skrypt DB dla potrzebnych tabel EF (mały podzbiór naszego całego DB), ponieważ nie będziemy używać EF do tworzenia lub tworzenia instancji DB (ponieważ nie obsługuje ona poprawnie migracji na razie...).

Mam szczerą nadzieję, że coś źle zinterpretowałem lub zapomniałem jakiegoś ustawienia w logice budującego model. Bo jeśli nie, projektanci EF zdefiniowali architekturę (podejście do projektowania TPT), której nie można zastosować w żadnej realnej sytuacji bez obejścia zabezpieczeń. To w połowie gotowe rozwiązanie. Nie zrozumcie mnie źle, podoba mi się praca, która została wykonana, i jak większość rozwiązań MSFT działa na 70% najbardziej podstawowych zastosowań aplikacji. Po prostu nie jest gotowy na bardziej złożone sytuacje.

Próbowałem zachować projekt DB w płynnym API EF i samodzielny. Dla mnie jest to 98%, byłoby miło, gdyby zakończyli pracę, może w następnym wydaniu. Przynajmniej to oszczędza mi wszystkich operacji CRUD.

Ciao! Jim Shaw

+0

Czy istnieje kaskadowe usuwanie do '' Chapter.Sections' Course.Chapters' i 'Section.Pages' i są te jeden-do-wielu relacje wymagane lub opcjonalny? Dla mnie wygląda to tak, jakby trzeba było wczytać strony Base do kontekstu, a następnie usunąć je explicitely i EF tworzy następnie dwie instrukcje DELETE (dla tabeli bazowej i pochodnej). Jeśli usuwanie polega na łańcuchu kaskadowych usunięć innych elementów, DB jest odpowiedzialny za usunięcie wszystkich powiązanych obiektów z odpowiednimi kaskadowymi usunięciami, których EF najwyraźniej nie tworzy. Nazwałbym to błędem lub przynajmniej jakimś ukrytym ograniczeniem, o którym trzeba wiedzieć. – Slauma

+0

Tak, jest. Próbowałem użyć kontekstu w logice Include pages, ale nadal nie działa. Postanowiłem zastosować rozwiązanie Add constraints, ponieważ nie będziemy/mogli używać logiki EF Dynamic DB tworzenia, ponieważ nie jest ona przyrostowa (być może po zakończeniu projektu migracji). Pisałem narzędzie generatora skryptów do użycia skryptu db pisać kontekst i niektóre automatycznie generowane skrypty kasowania w celu zwiększenia db skryptu). Będziemy używać oddzielnego pliku .sql do modelowania DB ... –

Odpowiedz

5

Mam powielana problem z trochę prostszy przykład:

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

To daje ten sam wyjątek, że miał zbyt. Tylko rozwiązania wydają się być:

  • Każda konfiguracja kaskadowe usuwanie do ograniczenia TPT w DB ręcznie, jak już testowane (lub umieścić odpowiednie polecenia SQL do metody Seed).
  • Lub załaduj obiekty, które biorą udział w dziedziczeniu TPT do pamięci. W moim przykładzie kodu:

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

    Kiedy podmioty są ładowane w kontekście EF tworzy właściwie dwa DELETE - jeden dla tabeli podstawowej i jeden dla tabeli pochodzą. W twoim przypadku jest to straszne rozwiązanie, ponieważ musisz załadować znacznie bardziej złożony wykres obiektów, zanim będziesz mógł uzyskać obiekty TPT.

Jeszcze bardziej problematyczne jest, jeśli Parent ma ICollection<DerivedPage> (i odwrotna Parent właściwość jest DerivedPage czym)

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

Przykład kodu nie będzie wyjątek, lecz usunięcie wiersza z wyprowadzona tabela, ale , a nie z tabeli podstawowej, pozostawiając wiersz fantomu, który nie może już reprezentować obiektu, ponieważ BasePage jest abstrakcyjny. Ten problem nie może zostać rozwiązany przez usuwanie kaskadowe, ale faktycznie trzeba było wczytać kolekcję do kontekstu, zanim można usunąć rodzica, aby uniknąć takich bzdur w bazie danych.

Podobna kwestia i analiza była tutaj: http://social.msdn.microsoft.com/Forums/en-US/adodotnetentityframework/thread/3c27d761-4d0a-4704-85f3-8566fa37d14e/

+0

Dzięki za informację. Postanowiłem użyć skryptów i skryptu ograniczającego auto/hand-cranked do tworzenia bazy danych. W ten sposób nadal mogę korzystać z EF dla operacji CRUD w naszej aplikacji (i są one raczej proste, usuwają kurs, usuwają rozdział ... i DB obsługuje się odpowiednio –

+0

Jest to rozwiązanie z wyzwalaczem i naprawdę struktura encji powinna albo obsłużyć to albo umieścić duże ostrzeżenie o takich funkcjach. – John

Powiązane problemy