2017-10-23 1 views
1

Ich habe eine komplexe Objekthierarchie in einer Unternehmensanwendung. Ich werde versuchen, es einfach und abstrakt zu halten, aber immer noch repräsentativ für das, womit ich es zu tun habe.Zuordnung der Referenzeigenschaft zum abstrakten Elternteil

Mein Projekt befasst sich mit mehreren Stilen desselben Objekttyps. Dafür haben wir die TPT Struktur für unsere Entitätsobjekten implementiert:

public abstract class BaseWidget { 
    public int Id { get; set; } 
    // etc... 
} 

// About a dozen concrete implementations already exist and work great! 
public class ExistingWidget : BaseWidget { 
    // Other properties 
} 

Jetzt habe ich eine neue Art, die ich tue. Wir haben gemeinsame Eigenschaften für das Objekt, aber je nach Subtyp sind ein paar unterschiedliche Sätze von Details erforderlich. Dazu richte ich TPH ein, da die Eigenschaften dieses Typs in allen Subtypen gleich sind. Der einzige Unterschied besteht darin, welche Detailobjekte benötigt werden.

public abstract NewWidgetBase : BaseWidget { 
    public int EmployeeNumber { get; set; } 
    public DateTime EffectiveDate { get; set; } 
} 

public NewWidgetA : NewWidgetBase { 
} 

public NewWidgetB : NewWidgetBase { 
} 

Ich habe dies in meinem DbContext wie folgt abgebildet:

protected override void OnModelCreating(DbModelBuilder modelBuilder) { 
    modelBuilder.Entity<NewWidgetBase>() 
       .Map<NewWidgetA>(w => w.Requires("Discriminator").HasValue("a")) 
       .Map<NewWidgetB>(w => w.Requires("Discriminator).HasValue("b")); 

An dieser Stelle habe ich einen Integrationstest verwendet und überprüft erfolgreich, dass ich in beiden Tabellen zu speichern.

Jetzt will ich in die Details hinzuzufügen:

public class FooDetails { 
    public int Id { get; set; } 
    public int NewWidgetId { get; set; } 
    // ... 
    [ForeignKey(nameof(NewWidgetId))] 
    public NewWidgetBase NewWidget { get; set; } 
} 

public class BarDetails { 
    public int Id { get; set; } 
    public int NewWidgetId { get; set; } 
    // ... 
    [ForeignKey(nameof(NewWidgetId))] 
    public NewWidgetBase NewWidget { get; set; } 
} 

ich dann diese Referenzeigenschaften auf meine entsprechenden NewWidget Objekte hinzufügen.

public class NewWidgetA { 
    // ... 
    public FooDetails Foo { get; set; } 
} 

public class NewWidgetB { 
    // ... 
    public FooDetails Foo { get; set; } 
    public BarDetails Bar { get; set; } 
} 

Ich habe gerade versucht, diese ausgeführt wird, unter der Annahme, dass der typische Mapping funktionieren würde, und bekam die folgende Fehlermeldung:

System.Data.Entity.Infrastructure.DbUpdateException: An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception. Handling of exceptions while saving can be made easier by exposing foreign key properties in your entity types. See the InnerException for details. ---> System.Data.Entity.Core.UpdateException: Unable to determine a valid ordering for dependent operations. Dependencies may exist due to foreign key constraints, model requirements, or store-generated values.

Damit habe ich verstanden, dass es nicht die richtige Beziehung Richtungen hat und Schlüssel zugeordnet. Also ging ich es nochmals ausdrücklich im DbContext einzustellen:

modelBuilder.Entity<NewWidgetA>() 
      .HasRequired(w => w.Foo) 
      .WithRequiredDependent(); 

jedoch, dass mir die Fehlermeldung gibt:

System.InvalidOperationException: A dependent property in a ReferentialConstraint is mapped to a store-generated column. Column: 'WidgetId'.

ich an einem „some other“ sah „questions“, und keine dieser Antworten half mir.

Als letzte Anstrengung, versuchte ich die Überladung für .WithRequiredDependent(), die eine Func. Da es sich jedoch nicht um genau denselben Typ handelt, den ich kartiere, weil ich die Eigenschaft als abstrakte Basis habe, beschwert sie sich. Deshalb versuche ich es wie so Casting:

modelBuilder.Entity<NewWidgetA>() 
      .HasRequired(w => w.Foo) 
      .WithRequiredDependent(f => (NewWidgetA)f.Widget); 

modelBuilder.Entity<NewWidgetB>() 
      .HasRequired(w => w.Foo) 
      .WithRequiredDependent(f => (NewWidgetB).Widget); 
modelBuilder.Entity<NewWidgetB>() 
      .HasRequired(w => w.Bar) 
      .WithRequiredDependent(b => (NewWidgetB).Widget); 

aber dies gibt auch einen Fehler:

The ForeignKeyAttribute on property 'Widget' on type '...Foo' is not valid. The foreign key name 'WidgetId' was not found on the dependent type 'NewWidgetA'. The Name value should be a comma separated list of foreign key property names.

Dieses mich führt zu glauben, dass ich nicht in der Lage bin zu tun, was ich tun möchte, mit abstrakte Eigenschaften haben. Gibt es eine Möglichkeit, diese Beziehung, die mir fehlt, abzubilden? Ich möchte keine spezifische Referenzeigenschaft für jeden haben, da ich weiß, dass innerhalb eines oder zweier Monate mehr Typen kommen, und die Liste der Eigenschaften wird unhandlich.

Antwort

1

Es ist möglich, aber nur mit unidirektionalen (mit Navigations Eigenschaft nur bei Widget Seite) eine Eins-zu-eins Shared Primary Key Association, wo die Widget Seite ist die Haupt und die Details Seite ist die abhängigen.

starten, indem Sie die Navigation und FK Eigenschaften von Details Einheiten zu entfernen:

public class FooDetails { 
    public int Id { get; set; } 
    // ... 
} 

public class BarDetails { 
    public int Id { get; set; } 
    // ... 
} 

und verwenden Sie die folgende fließend Konfiguration:

modelBuilder.Entity<NewWidgetA>() 
    .HasRequired(w => w.Foo) 
    .WithRequiredPrincipal(); 

modelBuilder.Entity<NewWidgetB>() 
    .HasRequired(w => w.Foo) 
    .WithRequiredPrincipal(); 

modelBuilder.Entity<NewWidgetB>() 
    .HasRequired(w => w.Bar) 
    .WithRequiredPrincipal(); 

Notiere die WithRequiredPrincipal() Anruf. Es sagt EF, dass (1) die Widget das Prinzip ist und (2) es keine Navigationseigenschaft von Details zu Widget gibt.

Das resultierende Datenbankschema ist so etwas wie folgt aus:

CreateTable(
    "dbo.BaseWidget", 
    c => new 
     { 
      Id = c.Int(nullable: false, identity: true), 
     }) 
    .PrimaryKey(t => t.Id); 

CreateTable(
    "dbo.ExistingWidget", 
    c => new 
     { 
      Id = c.Int(nullable: false), 
     }) 
    .PrimaryKey(t => t.Id) 
    .ForeignKey("dbo.BaseWidget", t => t.Id) 
    .Index(t => t.Id); 

CreateTable(
    "dbo.NewWidgetBase", 
    c => new 
     { 
      Id = c.Int(nullable: false), 
      EmployeeNumber = c.Int(nullable: false), 
      EffectiveDate = c.DateTime(nullable: false), 
      Discriminator = c.String(nullable: false, maxLength: 128), 
     }) 
    .PrimaryKey(t => t.Id) 
    .ForeignKey("dbo.BaseWidget", t => t.Id) 
    .Index(t => t.Id); 

CreateTable(
    "dbo.FooDetails", 
    c => new 
     { 
      Id = c.Int(nullable: false), 
      Data = c.String(), 
     }) 
    .PrimaryKey(t => t.Id) 
    .ForeignKey("dbo.NewWidgetBase", t => t.Id) 
    .Index(t => t.Id); 

CreateTable(
    "dbo.BarDetails", 
    c => new 
     { 
      Id = c.Int(nullable: false), 
      Data = c.String(), 
     }) 
    .PrimaryKey(t => t.Id) 
    .ForeignKey("dbo.NewWidgetBase", t => t.Id) 
    .Index(t => t.Id); 
+0

Awesome! Das macht Sinn, ist aber etwas kontraintuitiv. Ich werde es versuchen, sobald ich für den Tag arbeite. – krillgar

Verwandte Themen