Problem mit Viele-zu-Viele-Beziehung + TPH-Vererbung in Entity Framework 6

c# entity-framework entity-framework-6 many-to-many table-per-hierarchy

Frage

Ich stoße auf ein Problem mit EF6, obwohl ich ziemlich sicher bin, dass dies für frühere Versionen gilt, die diese Art der Zuordnung unterstützen. Ich fürchte, ich kenne die Antwort auf die Frage, aber ich hoffe, dass ich etwas falsch mache, oder es gibt eine bessere Problemumgehung als das, was ich hier vorstelle. Alle Klassen sind aus Gründen der Klarheit entkernt.

Also habe ich

public abstract class SoftwareFirmware
{
    public long Id { get; private set; }
    public ICollection<DeviceType> DeviceTypes { get; private set; } 

    public SoftwareFirmware()
    {
        DeviceTypes=new HashSet<DeviceType>();
    }
}

und

public class DeviceType
{
    public long Id { get; set; }
    public virtual ICollection<Firmware> AvailableFirmwareVerions { get; private set; }
    public virtual ICollection<Software> AvailableSoftwareVerions { get; private set; }

    public DeviceType()
    {
        AvailableFirmwareVerions = new HashSet<Firmware>();
        AvailableSoftwareVerions = new HashSet<Software>();
    }
}

die, wie Sie sehen können, eine Beziehung zwischen vielen und vielen haben. Ich habe zwei Klassen definiert, die von SoftwareFirmware , die passend benannt wurde

public class Firmware : SoftwareFirmware {}

und

public class Software : SoftwareFirmware {}

Ich verwende Table Per Hierarchy-Vererbung, also werden Software und Firmware in derselben Tabelle mit einer Diskriminatorspalte gespeichert. Schließlich habe ich die Beziehungen in der abgeleiteten DbContext OnModelCreating Methode mit zugeordnet

modelBuilder.Entity<DeviceType>().HasMany(d => d.AvailableFirmwareVerions).WithMany(firmware=>firmware.DeviceTypes);
modelBuilder.Entity<DeviceType>().HasMany(d => d.AvailableSoftwareVerions).WithMany(sofware=>sofware.DeviceTypes);

Das Problem liegt darin, dass Entity Framework die Vererbung mit dieser Zuordnung nicht unterstützt, da ich Folgendes erhalte, wenn EF versucht, die Datenbank zu generieren:

DeviceTypes: FromRole: NavigationProperty 'DeviceTypes' ist nicht gültig. Typ 'Software' von FromRole 'DeviceType_AvailableSoftwareVerions_Target' im AssociationType 'DeviceType_AvailableSoftwareVerions' muss genau mit dem Typ 'SoftwareFirmware' übereinstimmen, für den diese NavigationProperty deklariert ist.

Daraus entnehme ich, dass ein Typ, der von SoftwareFirmware erbt, für die NavigationProperty nicht gut genug ist, sondern ein SoftwareFirmware Typ sein muss. Wenn ich die DeviceType Sammlung aus der SoftwareFirmware Basisklasse DeviceType und sie in jeder der abgeleiteten Klassen dupliziere, funktionieren die Dinge, aber das ist sicherlich nicht ideal.

Meine Frage ist also - gibt es eine andere Möglichkeit, dies zu konfigurieren, damit ich meine Navigationseigenschaft in meiner Basisklasse behalten kann? Wenn nicht, gibt es eine sauberere Problemumgehung als das, was ich beschrieben habe?


UPDATE: So scheint es, dass SQL Server Management Studio mich falsch gemacht hat, da ich die Datenbank zuvor ohne die überladene Version von WithMany, die einen Ausdruck braucht und die Junction-Tabellen nicht enthielt, grafisch dargestellt hatte. Es scheint, als würde SSMS mit Schemaänderungen im Hinblick auf das Hinzufügen neuer Diagramme nicht gut funktionieren, selbst wenn die Datenbank gelöscht und neu erstellt wurde - sie muss neu gestartet werden. Großer Schmerz, aber ich schweife ab ...

Als letzten Versuch habe ich die parameterlose Version von WithMany für die Mappings wiederhergestellt, die Datenbank gelöscht und neu erstellt, indem ich die Anwendung neu gestartet habe, SSMS neu gestartet habe und lo! Die Junction-Tabellen wurden erstellt. Alles, was ich tun musste, ist ein Ignore für die DeviceTypes Eigenschaft der Basis- SoftwareFirmware Klasse und alles, was sauber generiert wurde, hinzuzufügen. Also sieht mein FluentAPI-Mapping-Code so aus:

modelBuilder.Entity<DeviceType>().HasMany(d => d.AvailableFirmwareVerions).WithMany();
modelBuilder.Entity<DeviceType>().HasMany(d => d.AvailableSoftwareVerions).WithMany();
modelBuilder.Entity<SoftwareFirmware>().Ignore(s => s.DeviceTypes);

das erzeugt dieses Schema - ziemlich genau das Schema, das ich wollte (ignorieren Sie die zusätzlichen Eigenschaften):

Bildbeschreibung hier eingeben

Da der parameterlose Aufruf von WithMany nur eine Navigationseigenschaft auf einer Seite WithMany , werden Aktualisierungen von Software.DeviceTypes und Firmware.DeviceTypes jedoch nicht von EF verfolgt, sodass ich wieder dort bin, wo ich angefangen habe.

Akzeptierte Antwort

Das Problem besteht darin, dass Sie eine einzelne SoftwareFirmware.DeviceTypes-Eigenschaft haben, die Sie dann jedoch als Teil von zwei separaten Beziehungen verwenden möchten. SoftwareFirmware.DeviceTypes kann nicht die Umkehrung von DeviceType.AvailableFirmwareVerions und DeviceType.AvailableSoftwareVerions sein.

Was du zu modellieren versuchst, ist ein bisschen komisch, weil du sie als unterschiedliche Beziehungen behandelst, aber auch nicht. Es gibt zwei Möglichkeiten hier ...

Option 1: Es sind zwei getrennte Beziehungen

Entfernen Sie SoftwareFirmware.DeviceTypes, und fügen Sie der Firmware und der Software eine DeviceTypes-Eigenschaft hinzu.

Dies ist tatsächlich, was Sie tun, wenn Sie Ignore auf die SoftwareFirmware.DeviceTypes-Eigenschaft setzen und die leere Überladung von WithMany verwenden - weshalb es funktioniert. Sie teilen EF mit, dass es zwei Beziehungen gibt (die eine Software -> DeviceType und die andere Firmware -> DeviceType) und dass es keine Navigationseigenschaft gibt, die zurück in die andere Richtung zeigt. Da Sie SoftwareFirmware.DeviceTypes ignoriert haben, ist es nicht Teil Ihres Modells.

Option 2: Es ist eine Beziehung

Entfernen Sie die zwei Navigationseigenschaften auf DeviceType und ersetzen Sie sie durch eine einzelne Navigation zu der SoftwareFirmware-Basisklasse. Sie können jederzeit einige Eigenschaften hinzufügen, die den Inhalt von Software und Firmware filtern (wie unten gezeigt).

public class DeviceType
{
    public long Id { get; set; }

    public virtual ICollection<SoftwareFirmware> AvailableVerions { get; private set; }

    public virtual IEnumerable<Firmware> AvailableFirmwareVerions
    {
        get
        {
            return this.AvailableVerions.OfType<Firmware>();
        }
    }

    public virtual IEnumerable<Software> AvailableSoftwareVerions
    {
        get
        {
            return this.AvailableVerions.OfType<Software>();
        }
    }

    public DeviceType()
    {
        AvailableVerions = new HashSet<SoftwareFirmware>();
    }
}

Beliebte Antwort

Dieses Problem kommt mir bekannt vor. (Überprüfung meiner E-Mail ... yep, es war vor über einem Jahr!) Ich hatte jemanden, der mir eine Probe schickte, wo eine fließende API-Beziehung versagte. Sie hatten nicht viele zu viele, aber ich denke, es ist das gleiche Problem. Ich verbrachte eine lange Zeit damit, es zu betrachten und fragte Rowan Miller (im Team) und er sagte, dass die fließende API die Eigenschaft, die vom Basistyp kommt, nicht verstehen kann.

Dh die flüssige API kann die DEVICETYPE-Eigenschaft nicht sehen, wenn sie nach AvailableSoftwareVerions oder nach AvailableFirmwareVersions sucht. (Ich kann Ihnen nicht sagen, WARUM das ist. Sie würden denken, es könnte es durch Reflexion finden, aber vielleicht wurde es einfach nicht mit diesem Szenario entworfen.)

Dies machte immer noch keinen Sinn für mich, also erklärte er weiter (und ich werde seine Erklärung mit Ihren Typen aktualisieren, was ein wenig verwirrend war, da Sie zusätzliche Vererbungsebenen haben und die Dinge ein wenig uneinheitlich benannt sind ... aber ich

Vom Konzept her machen die Klassen keinen Sinn, da ein DeviceType viele Software (s) oder Firmware (s) haben kann ... aber die Eigenschaft der inversen Navigation ist auf SoftwareFirmware definiert. Was passiert also, wenn etwas, das keine Firmware oder Software ist, einen DeviceType hat? Es ist invers als> DeviceType.AvailableSoftwareVersions konfiguriert, aber das kann nicht funktionieren. Selbst wenn EF aus dem Bild genommen wird, ist die richtige Methode zum Modell, dass sich die Projekteigenschaft im Bericht befindet.

Das war mit EF5. Wenn mein Speicher korrekt ist und es sich um das gleiche Problem handelt, hat sich das für EF6 möglicherweise nicht geändert. Vielleicht sollten wir nachsehen, ob da ein Problem zur Lösung dieses Problems besteht. Seine weitere Erklärung deutet jedoch darauf hin, dass es sich nicht um einen Bug, sondern um einen Schutz handelt.

(Ich werde ihn anpingen, um zu verifizieren, dass ich das vorhergehende Problem richtig zu diesem Schluss folge).

In dieser E-Mail schlug Rowan außerdem vor, Getter-Logik anstelle von Navigationseigenschaften als Workaround zu verwenden.



Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Ist diese KB legal? Ja, lerne warum
Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Ist diese KB legal? Ja, lerne warum