Problème lié à la relation plusieurs-à-plusieurs + héritage TPH dans Entity Framework 6

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

Question

Je rencontre un problème avec EF6, bien que je sois à peu près sûr que cela s'applique aux versions précédentes qui prennent en charge ce type de mappage. Je crains de connaître la réponse à la question posée, mais j'espère que je fais quelque chose de mal ou qu'il existe une meilleure solution de contournement que ce que je présente ici. Toutes les classes sont vidées pour plus de clarté.

Donc j'ai

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

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

et

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

qui, comme vous pouvez le voir, ont une relation plusieurs à plusieurs définie. J'ai défini deux classes dérivées de SoftwareFirmware , le bien nommé

public class Firmware : SoftwareFirmware {}

et

public class Software : SoftwareFirmware {}

J'utilise l'héritage Table par hiérarchie. Le Software et le Firmware sont donc stockés dans la même table avec une colonne discriminante. Enfin, j'ai mappé les relations dans la méthode OnModelCreating DbContext dérivée avec

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

Le problème est qu'Entity Framework ne semble pas prendre en charge l'héritage avec ce mappage, car je reçois les informations suivantes lorsque EF tente de générer la base de données:

DeviceTypes: FromRole: NavigationProperty 'DeviceTypes' n'est pas valide. Le type 'Software' de FromRole 'DeviceType_AvailableSoftwareVerions_Target' dans AssociationType 'DeviceType_AvailableSoftwareVerions' doit correspondre exactement au type 'SoftwareFirmware' sur lequel cette propriété de navigation est déclarée.

J'en déduis qu'un type qui hérite de SoftwareFirmware n'est pas suffisant pour NavigationProperty, il doit s'agir d'un type SoftwareFirmware . Si je déchire la collection DeviceType de la classe de base SoftwareFirmware et la duplique dans chacune des classes dérivées, tout fonctionne, mais c'est loin d'être idéal.

En conclusion, ma question est la suivante: existe-t-il un autre moyen de configurer cela afin que je puisse conserver ma propriété de navigation dans ma classe de base? Si ce n'est pas le cas, existe-t-il une solution plus propre que celle que j'ai décrite?


UPDATE: il semblerait donc que SQL Server Management Studio me soit trompé, car j’avais précédemment tracé la base de données sans la version surchargée de WithMany qui prenait une expression et n’incluait pas les tables de jonction. Il semble que SSMS ne joue pas bien avec les modifications de schéma en termes d’ajout de nouveaux diagrammes même lorsque la base de données a été supprimée et recréée - elle doit être redémarrée. Douleur majeure, mais je m'éloigne du sujet ...

Comme dernier effort, je suis revenu à la version sans paramètre de WithMany pour les mappages, puis WithMany supprimé et recréé la base de données en redémarrant l'application, redémarré SSMS et lo! Les tables de jonction ont été créées. Tout ce que j'avais à faire, c'était d'ajouter un Ignore à la propriété DeviceTypes la classe de base SoftwareFirmware et à tout ce qui était généré proprement. Donc, mon code de mappage FluentAPI ressemble à ceci:

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

qui génère ce schéma - à peu près exactement le schéma que je voulais (ignorer les propriétés supplémentaires):

entrez la description de l'image ici

Cependant, étant donné que l'appel sans paramètre à WithMany ne WithMany qu'une propriété de navigation d'un côté, les mises à jour de Software.DeviceTypes et de Firmware.DeviceTypes ne sont pas suivies par EF, je suis donc de retour à mon point de départ.

Réponse acceptée

Le problème est que vous avez une propriété unique SoftwareFirmware.DeviceTypes mais que vous essayez ensuite de l'utiliser dans le cadre de deux relations distinctes. SoftwareFirmware.DeviceTypes ne peut pas être l'inverse de DeviceType.AvailableFirmwareVerions et de DeviceType.AvailableSoftwareVerions.

Ce que vous essayez de modéliser est un peu étrange parce que vous les traitez en quelque sorte comme des relations distinctes, mais également pas. Il y a deux options ici...

Option 1: deux relations distinctes

Supprimez SoftwareFirmware.DeviceTypes et ajoutez une propriété DeviceTypes sur le micrologiciel et le logiciel.

C’est ce que vous faites lorsque vous mettez Ignore sur la propriété SoftwareFirmware.DeviceTypes et que vous utilisez la surcharge vide de WithMany - c’est pourquoi il fonctionne. Vous dites à EF qu'il existe deux relations (un logiciel -> DeviceType et l'autre Firmware -> DeviceType) et qu'il n'y a pas de propriété de navigation qui pointe en arrière. Comme vous avez ignoré SoftwareFirmware.DeviceTypes, cela ne fait tout simplement pas partie de votre modèle.

Option 2: c'est une relation

Supprimez les deux propriétés de navigation sur DeviceType et remplacez-les par une navigation unique vers la classe de base SoftwareFirmware. Vous pouvez toujours ajouter des propriétés de façade qui filtrent le contenu en logiciels et micrologiciels (comme indiqué ci-dessous).

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

Réponse populaire

Ce problème semble familier. (en vérifiant mon adresse e-mail… oui, c'était il y a plus d'un an!) Quelqu'un m'a envoyé un échantillon où une relation api fluide échouait. Ils n’en avaient pas beaucoup, mais je pense que c’est le même problème. J'ai passé un long moment à le regarder et j'ai demandé à Rowan Miller (de l'équipe) de me dire que l'API fluide ne pouvait pas comprendre la propriété provenant du type de base.

c'est-à-dire que l'API fluide ne peut pas voir la propriété DEVICETYPE quand on regarde AvailableSoftwareVerions ou AvailableFirmwareVersions. (Je ne peux pas vous dire POURQUOI ceci est. On pourrait penser qu'il pourrait le trouver par réflexion, mais peut-être qu'il n'a tout simplement pas été conçu en tenant compte de ce scénario.)

Cela n’avait toujours pas de sens pour moi alors il a expliqué plus loin (et je mettrai à jour son explication avec vos types, ce qui était un peu déroutant puisque vous avez des niveaux supplémentaires d’héritage et que les noms sont nommés de

Conceptuellement, les classes n'ont pas vraiment de sens, car un DeviceType peut avoir plusieurs logiciels ou micrologiciels ... mais la propriété de navigation inverse est définie sur SoftwareFirmware. Alors que se passe-t-il quand quelque chose qui n'est pas un micrologiciel ou un logiciel a un type de périphérique? C'est l'inverse qui est configuré comme> DeviceType.AvailableSoftwareVersions mais cela ne peut pas fonctionner. Même en supprimant EF de l’image, la bonne façon de modéliser le fait que la propriété Project soit dans Report.

C'était avec EF5. Si ma mémoire est correcte et que c'est le même problème, alors peut-être que cela n'a pas changé pour EF6. Peut-être devrions-nous chercher à voir s’il existe un problème pour résoudre ce problème. Cependant, ses explications supplémentaires suggèrent que ce n'est pas un bug mais une protection.

(Je vais lui envoyer une requête pour vérifier que je déduis correctement le problème précédent).

Dans cet e-mail, Rowan a également suggéré d'utiliser la logique de lecture au lieu des propriétés de navigation comme solution de contournement.



Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi