Problema con relazione molti-a-molti + ereditarietà TPH in Entity Framework 6

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

Domanda

Ho riscontrato un problema con EF6, anche se sono abbastanza sicuro che questo si applica alle versioni precedenti che supportano questo tipo di mapping. Temo di conoscere la risposta alla domanda in questione, ma spero che stia facendo qualcosa di sbagliato o che ci sia una soluzione migliore di quella che presento qui. Tutte le classi sono sventrate per chiarezza.

Quindi ho

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

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

e

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

che come potete vedere hanno definito una relazione molte a molte. Ho definito due classi che derivano da SoftwareFirmware , il nome appropriato

public class Firmware : SoftwareFirmware {}

e

public class Software : SoftwareFirmware {}

Sto usando l'ereditarietà di Table Per Hierarchy, quindi Software e Firmware sono memorizzati nella stessa tabella con una colonna discriminator. Infine, ho mappato le relazioni nel metodo OnModelCreating DbContext derivato con

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

Il problema in questione è che Entity Framework non sembra supportare l'ereditarietà con questa mappatura, poiché ricevo quanto segue quando EF cerca di generare il database:

DeviceTypes: FromRole: NavigationProperty 'DeviceTypes' non è valido. Digitare "Software" di FromRole "DeviceType_AvailableSoftwareVerions_Target" in AssociationType "DeviceType_AvailableSoftwareVerions" deve corrispondere esattamente al tipo "SoftwareFirmware" su cui viene dichiarata la proprietà NavigationProperty.

Da ciò deduco che un tipo che eredita da SoftwareFirmware non è abbastanza buono per NavigationProperty, deve essere un tipo SoftwareFirmware . Se strappo la collezione DeviceType dalla classe base SoftwareFirmware e la duplica in ciascuna delle classi derivate, le cose funzionano, ma questo è certamente meno ideale.

Quindi, alla fine, la mia domanda è: c'è un altro modo per configurarlo in modo da poter mantenere la mia proprietà di navigazione nella mia classe base? In caso contrario, c'è un workaround più pulito di quello che ho descritto?


AGGIORNAMENTO: quindi sembrerebbe che SQL Server Management Studio mi abbia fatto un errore, in quanto avevo precedentemente creato un diagramma del database senza la versione sovraccaricata di WithMany che accetta un'espressione e non includeva le tabelle di giunzione. Sembra che SSMS non giochi bene con le modifiche dello schema in termini di aggiunta di nuovi diagrammi anche quando il database è stato abbandonato e ricreato - deve essere riavviato. Maggiore dolore, ma sto divagando ...

Come ultimo tentativo, sono tornato alla versione senza parametri di WithMany per i mapping, cancellato e ricreato il database riavviando l'applicazione, riavviato SSMS e lo! Le tabelle di congiunzione sono state create. Tutto quello che dovevo fare è aggiungere una Ignore per la proprietà DeviceTypes della classe SoftwareFirmware base e tutto generato in modo pulito. Quindi il mio codice di mappatura FluentAPI si presenta così:

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

che genera questo schema, praticamente lo schema che volevo (ignora le proprietà extra):

inserisci la descrizione dell'immagine qui

Tuttavia, poiché la chiamata senza parametri a WithMany si WithMany solo a una proprietà di navigazione su un lato, gli aggiornamenti a Software.DeviceTypes e Firmware.DeviceTypes non vengono tracciati da EF, quindi sono tornato dove ho iniziato.

Risposta accettata

Il problema è che si dispone di una singola proprietà SoftwareFirmware.DeviceTypes ma si sta tentando di utilizzarla come parte di due relazioni separate. SoftwareFirmware.DeviceTypes non può essere l'inverso di entrambi DeviceType.AvailableFirmwareVerions e DeviceType.AvailableSoftwareVerions.

Quello che stai cercando di modellare è un po 'strano perché ti stai trattando come relazioni distinte, ma anche no. Ci sono due opzioni qui ...

Opzione 1: sono due relazioni separate

Rimuovere SoftwareFirmware.DeviceTypes e aggiungere una proprietà DeviceTypes su firmware e software.

Questo è ciò che si sta facendo quando si inserisce Ignora nella proprietà SoftwareFirmware.DeviceTypes e si utilizza il sovraccarico vuoto di WithMany, che è il motivo per cui funziona. Stai dicendo a EF che ci sono due relazioni (un software -> DeviceType e l'altro firmware -> DeviceType) e che non esiste una proprietà di navigazione che punti indietro nell'altro modo. Dal momento che hai ignorato SoftwareFirmware.DeviceTypes, non fa parte del tuo modello.

Opzione 2: è una relazione

Rimuovere le due proprietà di navigazione su DeviceType e sostituirle con una singola navigazione sulla classe base SoftwareFirmware. È sempre possibile aggiungere alcune proprietà façade che filtrano i contenuti su Software e Firmware (come mostrato di seguito)

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

Risposta popolare

Questo problema sembra familiare. (controllando la mia e-mail ... sì, era passato più di un anno fa!) Avevo qualcuno che mi mandava un campione in cui una relazione con API fluente stava fallendo. Non ne avevano molti a molti, ma penso che sia lo stesso problema. Ho passato molto tempo a guardarlo e ho chiesto a Rowan Miller (nel team) e ha detto che l'api fluente non può comprendere la proprietà proveniente dal tipo di base.

cioè l'API fluente non può vedere la proprietà DEVICETYPE quando sta guardando AvailableSoftwareVerions o AvailableFirmwareVersions. (Non posso dirti perché è così. Penseresti che potrebbe trovarlo tramite la riflessione, ma forse non è stato progettato pensando a questo scenario).

Questo non aveva ancora senso per me, così ha spiegato ulteriormente (e aggiornerò la sua spiegazione con i tuoi tipi che era un po 'confusa dato che hai livelli extra di ereditarietà e le cose si chiamano un po' incoerentemente ... ma io

Concettualmente le classi non hanno molto senso, perché un DeviceType può avere molti Software o Firmware (s) ... ma la proprietà di navigazione inversa è definita su SoftwareFirmware. Quindi cosa succede quando qualcosa che non è un firmware o un software ha un DeviceType? L'inverso è configurato come> DeviceType.AvailableSoftwareVersions ma non può funzionare. Anche prendendo EF fuori dall'immagine, il modo corretto per modellare quello è avere la proprietà Project in Report.

Questo è stato con EF5. Se la mia memoria è corretta ed è lo stesso problema, allora forse non è cambiato per EF6. Forse dovremmo cercare di vedere se c'è un problema lì dentro per risolvere questo problema. Tuttavia, la sua ulteriore spiegazione suggerisce che non è un bug ma una protezione.

(Vado a pingarlo per verificare che sto inferendo il problema precedente a questo correttamente).

In quell'email, Rowan suggerì anche di usare la logica getter invece delle proprietà di navigazione come soluzione alternativa.



Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché
Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché