2014-04-21 25 views
9

Próbuję utworzyć 2 relacje jeden-do-jednego między klasami pochodnymi wspólnej bazy i niezwiązanej klasy, tak aby kiedy Usuwam wiersz nadrzędny, w którym wiersze podrzędne w bazie danych są usuwane. Rozmyślałem nad tym problemem od kilku dni i próbowałem każdej (dla mnie) możliwej do wyobrażenia kombinacji relacji w płynnym interfejsie API. Jak dotąd bez satysfakcjonujących rezultatów. To jest moja konfiguracja:Entity Framework 6.1 Code First Cascading Delete with TPH dla relacji jeden-do-jednego na typie pochodnym

public class OtherType 
{ 
public int ID {get; set;} 

public int? DerivedTypeAID {get; set;} 
public virtual DerivedTypeA DerivedType {get; set;} 

public int? DerivedTypeBID {get; set;} 
public virtual DerivedTypeB DerivedType {get; set;} 
} 


public abstract class BaseType 
{ 
    public int ID {get; set;} 
    public string ClassName {get; set;} 
    public virtual OtherType {get; set;} 
} 


public class DerivedTypeA : BaseType 
{ 
public string DerivedProperty {get; set;} 
} 

public class DerivedTypeB : BaseType 
{ 
public string DerivedProperty {get; set;} 
} 

public class MyContext : DbContext 
{ 
public MyContext() 
     : base("name=MyContext") 
    { 
    } 

    public DbSet<OtherType> OtherTypes { get; set; } 
    public DbSet<BaseType> BaseTypes { get; set; } 



    protected override void OnModelCreating(DbModelBuilder modelBuilder) 
    { 
     var m = modelBuilder; 

     m.Entity<OtherType>().HasOptional(_ => _.DerivedTypeA) 
      .WithMany().HasForeignKey(_ => _.DerivedTypeAID).WillCascadeOnDelete(true); 
     m.Entity<OtherType>().HasOptional(_ => _.DerivedTypeB) 
      .WithMany().HasForeignKey(_ => _.DerivedTypeBID).WillCascadeOnDelete(true); 

     m.Entity<DerivedTypeA>().HasRequired(_ => _.OtherType).WithMany().HasForeignKey(_ => _.ID).WillCascadeOnDelete(true); 
     m.Entity<DerivedTypeB>().HasRequired(_ => _.OtherType).WithMany().HasForeignKey(_ => _.ID).WillCascadeOnDelete(true); 
    } 
} 

To działa w pełni, z wyjątkiem kaskadowej części usuwania. EF tworzy klucze obce w tabeli nadrzędnej OtherType dla każdego odnośnika DerivedType z DELETE CASCADE. W tabeli podrzędnej (TPH => BaseTypes) tworzy on jeden klucz obcy z DELETE RESTRICT. Spodziewam się, że dwa ostatnie wiersze w moim kodzie utworzą pożądane klucze obce przy pomocy polecenia DELETE CASCADE. Ponieważ jest to najbliższy moment, w którym zadziałało (i nie mam żadnej z moich wcześniejszych prób uratowanych), zostawię to i mam nadzieję, że wszystko wyjaśniłem wystarczająco dobrze, aby ktoś mógł wskazać mi właściwy kierunek . Dzięki!

Aktualizacja # 1

Mam teraz przesunięte w kierunku używając EF TPC nadzieję być w stanie rozwiązać mój problem w ten sposób. To nie mialo miejsca. A więc o to chodzi o nieco więcej szczegółów i ERD wyjaśniając mój problem na nowo, mając nadzieję, że ktoś może mi pomóc, ponieważ dochodzę do tego stanu, w którym zaczynasz śmiać się histerycznie, wyciągając włosy. To byłby mój EF Model:

Ów jak poszedłem o tworzeniu że z kodem pierwszy:

public abstract class BaseType 
{ 
    public int BaseTypeId { get; set; } 
} 

public class DerivedTypeA : BaseType 
{ 
    public virtual OtherType OtherType { get; set; } 
} 

public class DerivedTypeB : BaseType 
{ 
    public virtual OtherType OtherType { get; set; } 
} 

public class OtherType 
{ 
    public int Id { get; set; } 

    public virtual DerivedTypeA DerivedTypeA { get; set; } 

    public virtual DerivedTypeB DerivedTypeB { get; set; } 
} 

public class TPCModel : DbContext 
{ 

    public TPCModel() 
     : base("name=TPCModel") 
    { 

    } 

    public DbSet<BaseType> BaseTypes { get; set; } 
    public DbSet<OtherType> OtherTypes { get; set; } 

    protected override void OnModelCreating(DbModelBuilder modelBuilder) 
    { 
     var m = modelBuilder; 

     m.Entity<BaseType>().Property(_ => _.BaseTypeId) 
      .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None); 

     m.Entity<DerivedTypeA>().Map(x => 
      { 
       x.MapInheritedProperties(); 
       x.ToTable("DerivedTypeA"); 
      }); 

     m.Entity<DerivedTypeB>().Map(x => 
      { 
       x.MapInheritedProperties(); 
       x.ToTable("DerivedTypeB"); 
      }); 

     m.Entity<DerivedTypeA>().HasRequired(_ => _.OtherType) 
      .WithOptional(_ => _.DerivedTypeA).WillCascadeOnDelete(true); 

     m.Entity<DerivedTypeB>().HasRequired(_ => _.OtherType) 
      .WithOptional(_ => _.DerivedTypeB).WillCascadeOnDelete(true); 

    } 


} 

Z tym kodem to schemat bazy danych został stworzony:

Zarówno DerivedTypes-Tabele mają swój klucz podstawowy, który jest również klucz wstępny odnoszący się do BaseTypeId-Kolumny na BaseTypes.

Do tworzenia że kod najpierw Śledziłem kierunki stąd: - http://weblogs.asp.net/manavi/archive/2011/01/03/inheritance-mapping-strategies-with-entity-framework-code-first-ctp5-part-3-table-per-concrete-type-tpc-and-choosing-strategy-guidelines.aspx
- http://msdn.microsoft.com/en-us/data/jj591620#RequiredToOptional

próbuję popełnić rekordy do bazy danych przy użyciu tego kodu:

using (var ctx = new TPCModel()) 
{ 
    Database.SetInitializer(new DropCreateDatabaseAlways<TPCModel>()); 
    ctx.Database.Initialize(true); 

    ctx.OtherTypes.Add(new OtherType() 
    { 
     DerivedTypeA = new DerivedTypeA() { BaseTypeId=1 }, 
     DerivedTypeB = new DerivedTypeB() { BaseTypeId=2 } 
    }); 

    ctx.SaveChanges(); // Exception throws here 
} 

EF rzuca ten wyjątek wiadomość :

Additional information: The changes to the database were committed successfully, but an error occurred while updating the object context. The ObjectContext might be in an inconsistent state. Inner exception message: Saving or accepting changes failed because more than one entity of type 'EF6.CodeFirst.TPC.Model.SecondConcreteType' have the same primary key value. Ensure that explicitly set primary key values are unique. Ensure that database-generated primary keys are configured correctly in the database and in the Entity Framework model. Use the Entity Designer for Database First/Model First configuration. Use the 'HasDatabaseGeneratedOption" fluent API or 'DatabaseGeneratedAttribute' for Code First configuration. 

Teraz, jak do rozwiązania w tej niefortunnej sytuacji powinien wyglądać tak, jakbym miał całkiem dobre pojęcie. Podczas korzystania ze strategii tabeli na klasy (TPC) trzeba samemu obsłużyć generowanie kluczy podstawowych, więc EF nie jest zdezorientowany, gdy masz dwa identyczne klucze podstawowe dla całkowicie niepowiązanych tabel na poziomie bazy danych, ale które mają wspólną klasę podstawową na poziomie EF.Proponowany sposób w pierwszym URL-ie, który podłączyłem, niestety nie zmniejsza tego problemu, ponieważ klucze obce na moim DerivedType -Objects będą w końcu takie same tutaj, niezależnie od tego, skoro odnoszą się do klucza podstawowego na OtherTypes-Tabeli, która oczywiście będzie to samo dla wyraźnego zapisu w tej tabeli. To jest problem.

Rozwiązanie, które zakładam, wymagałoby dwóch dodatkowych kolumn w tabeli OtherTypes, z których każda jest celem jednego z kluczy obcych w tabelach DerivedTypes. Ale absolutnie nie wiem jak to zaimplementować w EF. Cokolwiek próbowałem do tej pory, zwykle kończyło się pewnym wyjątkiem, w jaki sposób możesz mieć tylko hierarchiczne niezależne asocjacje dla większości wyprowadzonych typów (które w rzeczywistości są DerivedTypeA i DerivedTypeB) lub innych wyjątków sprawdzania poprawności narzekających na to, że krotność musi być many na jednym z kończy się związek.

Muszę zaznaczyć, że rodzaj utworzonego przeze mnie modelu jest dokładnie tym, czego potrzebuję, ponieważ pracuję w ramach większego modelu, który wykorzystuje system rekurencyjny i AutoMapper do mapowania między dwiema warstwami. Powiedziałem, że chciałbym poprosić cię o znalezienie rozwiązania dla proponowanego modelu i nie wymyślić innego rodzaju modelu lub obejścia, w przypadku których odbiegam od mapowania zero..one to one.

UPDATE # 2

Byłem krwawy dość kłopotów EF6 do tworzenia skojarzeń z klas pochodnych w obrębie hierarchii dziedziczenia innych niepowiązanych-w-rodzaju zajęć, więc poszedł do przodu i całkowicie przepisał mój model danych do wykluczyć jakąkolwiek hierarchię (bez TPH/TPT/TPC). Zrobiłem to w około 5 godzin wraz ze wszystkimi mapowaniami, używając płynnego api i siewu dla całego spektrum stołów. Kaskadowanie usuwa pracę dokładnie, ponieważ ustawiam je z każdego miejsca w moim modelu. Nadal nie rezygnowałbym z usłyszenia czyjegoś rozwiązania tego problemu, ale nadal bym żył na wypadek, gdyby to nie zostało rozwiązane.

+0

+1: mam dokładnie ten sam problem teraz . Chciałbym rozwiązanie tego. –

Odpowiedz

0

Myślę, że Twój problem jest związany z typem właściwości nawigacji w klasie OtherType.

Nie sądzę, aby w tym scenariuszu można było uzyskać silnie typowane właściwości.

Ma to główną przyczynę w cyklicznej kaskadzie, którą usuwa twój model.

Jako wtórnym obejście, skoro już znalazł się jeden, spróbuj model poniżej, że kiedyś w podobnej sytuacji (z Person = OtherType, PersonDetail = BaseType, HumanBeing = DerivedTypeA, Corporation = DerivedTypeB)

public class Person 
{ 
    public Guid Id { get; set; } 

    public string Designation { get; set; } 

    public virtual PersonDetail Detail { get; set; } 

    public virtual Person AggregatedOn { get; set; } 

    protected ICollection<Person> aggregationOf; 
    public virtual ICollection<Person> AggregationOf 
    { 
     get { return aggregationOf ?? (aggregationOf = new HashSet<Person>()); } 
     set { aggregationOf = value; } 
    } 
} 

public abstract class PersonDetail 
{ 
    public Guid Id { get; set; } 

    public virtual Person Personne { get; set; } 
} 

public class Corporation : PersonDetail 
{ 
    public string Label { get; set; } 
} 

public class HumanBeing : PersonDetail 
{ 
    public string FirstName { get; set; } 
    public string LastName { get; set; } 
} 

public class ReferentialContext : DbContext 
{ 
    public ReferentialContext() 
     : base("ReferentialContext") 
    { 
    } 

    public ReferentialContext(string nameOrConnectionString) 
     : base(nameOrConnectionString) 
    { 
    } 

    public DbSet<Person> Personnes { get; set; } 

    protected override void OnModelCreating(DbModelBuilder modelBuilder) 
    { 
     base.OnModelCreating(modelBuilder); 

     modelBuilder.Types().Configure(t => t.ToTable(t.ClrType.Name.ToUpper())); 
     modelBuilder.Properties().Configure(p => p.HasColumnName(p.ClrPropertyInfo.Name.ToUpper())); 

     modelBuilder.Configurations.Add(new PersonConfiguration()); 
     modelBuilder.Configurations.Add(new PersonDetailConfiguration()); 

    } 
} 

class PersonConfiguration : EntityTypeConfiguration<Person> 
{ 
    public PersonConfiguration() 
    { 
     this.HasMany(p => p.AggregationOf) 
      .WithOptional(p => p.AggregatedOn); 
    } 
} 

class PersonDetailConfiguration : EntityTypeConfiguration<PersonDetail> 
{ 
    public PersonDetailConfiguration() 
    { 
     this.Property(p => p.Id) 
      .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None); 

     this.HasRequired(p => p.Personne) 
      .WithRequiredDependent(p => p.Detail); 
    } 
} 

Jedyną różnicą, jaką widzę pomiędzy twoim modelem a moim, jest to, że nie "dbam" o typ rzeczywistej własności w mojej osobie (OtherType), ponieważ zawsze mogę użyć funkcji OfType linq, aby sprawdzić, czy Osobami w moich nieruchomościach są ludzie (DerivedTypeA) lub korporacje (DerivedTypeB).

0

ostatnia linia exeption

Użyj „HasDatabaseGeneratedOption” fluent API lub 'DatabaseGeneratedAttribute' Kod dla pierwszej konfiguracji.

Więc dodać [key] [DatabaseGenerated (DatabaseGeneratedOption.Identity)] do ids

i dodać [ForeignKey ("nazwa collumn")], aby właściwości nawigacyjnych

uzyskać lepsze instrukcje znaleźć i przeczytać te książki online:

  • Programowanie Entity Framework: DbContext, ISBN : 978-1-4493-1296-1
  • Microsoft ADO.NET Entity Framework krok po kroku, ISBN: 978-0-73566-416-6