Problema con la relación muchos a muchos + herencia de TPH en Entity Framework 6

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

Pregunta

Estoy teniendo un problema con EF6, aunque estoy bastante seguro de que esto se aplica a versiones anteriores que admiten este tipo de mapeo. Me temo que sé la respuesta a la pregunta en cuestión, pero espero que esté haciendo algo mal o que haya una solución mejor que la que presento aquí. Todas las clases se destruyen para mayor claridad.

Así que tengo

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

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

y

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

que como pueden ver tienen una relación de muchos a muchos definidos. He definido dos clases que se derivan de SoftwareFirmware , el nombre apropiado

public class Firmware : SoftwareFirmware {}

y

public class Software : SoftwareFirmware {}

Estoy usando la herencia de tabla por jerarquía, por lo que el Software y el Firmware se almacenan en la misma tabla con una columna discriminadora. Finalmente, he mapeado las relaciones en el método OnModelCreating DbContext derivado con

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

El problema actual es que Entity Framework no parece admitir la herencia con esta asignación, ya que recibo lo siguiente cuando EF intenta generar la base de datos:

DeviceTypes: FromRole: NavigationProperty 'DeviceTypes' no es válido. Escriba 'Software' de FromRole 'DeviceType_AvailableSoftwareVerions_Target' en AssociationType 'DeviceType_AvailableSoftwareVerions' debe coincidir exactamente con el tipo 'SoftwareFirmware' en el que se declara esta propiedad de navegación.

De aquí deduzco que un tipo que hereda de SoftwareFirmware no es lo suficientemente bueno para la propiedad de navegación, debe ser un tipo de SoftwareFirmware . Si DeviceType colección DeviceType de la clase base de SoftwareFirmware y la DeviceType en cada una de las clases derivadas, las cosas funcionan, pero eso no es lo ideal.

Entonces, finalmente, mi pregunta es: ¿hay otra forma de configurarlo para que pueda mantener mi propiedad de navegación en mi clase base? Si no, ¿hay una solución más limpia que la que he descrito?


ACTUALIZACIÓN: por lo que parecería que SQL Server Management Studio me hizo mal, ya que había diagramado la base de datos anteriormente sin la versión sobrecargada de WithMany que toma una expresión y no incluía las tablas de unión. Parece que SSMS no funciona bien con los cambios de esquema en términos de agregar nuevos diagramas, incluso cuando la base de datos se ha eliminado y recreado, debe reiniciarse. Dolor importante, pero estoy divagando ...

Como último esfuerzo, volví a la versión sin parámetros de WithMany para las asignaciones, WithMany y WithMany a crear la base de datos reiniciando la aplicación, reiniciando SSMS, ¡y he aquí! Se crearon las tablas de unión. Todo lo que tenía que hacer es agregar un Ignore para la propiedad DeviceTypes la clase de SoftwareFirmware base y todo lo que se generó limpiamente. Así que mi código de mapeo FluentAPI se ve así:

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

que genera este esquema, casi exactamente el esquema que quería (ignorar las propiedades adicionales):

introduzca la descripción de la imagen aquí

Sin embargo, dado que la llamada sin parámetros a WithMany solo conecta una propiedad de navegación en un lado, las actualizaciones de Software.DeviceTypes y Firmware.DeviceTypes no son rastreadas por EF, por lo que estoy de vuelta donde empecé.

Respuesta aceptada

El problema es que tiene una sola propiedad SoftwareFirmware.DeviceTypes pero luego intenta usarla como parte de dos relaciones separadas. SoftwareFirmware.DeviceTypes no puede ser el inverso de DeviceType.AvailableFirmwareVerions y DeviceType.AvailableSoftwareVerions.

Lo que estás tratando de modelar es un poco extraño porque los estás tratando como relaciones distintas, pero también no. Aquí hay dos opciones...

Opción 1: son dos relaciones separadas

Elimine SoftwareFirmware.DeviceTypes y agregue una propiedad DeviceTypes en Firmware y Software.

Esto es realmente lo que estás haciendo cuando pones Ignorar en la propiedad SoftwareFirmware.DeviceTypes y usas la sobrecarga vacía de WithMany, razón por la cual funciona. Le está diciendo a EF que hay dos relaciones (un Software -> DeviceType y el otro Firmware -> DeviceType) y que no hay ninguna propiedad de navegación que apunte hacia el otro lado. Dado que ignoró SoftwareFirmware.DeviceTypes, simplemente no forma parte de su modelo.

Opción 2: es una relación

Elimine las dos propiedades de navegación en DeviceType y reemplácelas con una sola navegación a la clase base de SoftwareFirmware. Siempre puede agregar algunas propiedades de fachada que filtran los contenidos a Software y Firmware (como se muestra a continuación)

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

Respuesta popular

Este problema suena familiar. (revisando mi correo electrónico ... ¡sí, fue hace más de un año!). Alguien me envió una muestra donde una relación fluida de api estaba fallando. No tenían muchos a muchos, pero creo que es el mismo problema. Pasé mucho tiempo mirándolo y le pregunté a Rowan Miller (en el equipo) y él dijo que la API fluida no puede comprender la propiedad que proviene del tipo base.

es decir, la API fluida no puede ver la propiedad DEVICETYPE cuando está mirando AvailableSoftwareVerions o AvailableFirmwareVersions. (No puedo decir POR QUÉ es esto. Pensarías que podría encontrarlo a través de la reflexión, pero tal vez no fue diseñado teniendo en cuenta este escenario).

Esto todavía no tenía sentido para mí, así que me explicó más (y actualizaré su explicación con sus tipos, lo cual fue un poco confuso, ya que tiene niveles adicionales de herencia y las cosas se nombran de forma un tanto inconsistente ... pero

Conceptualmente, las clases realmente no tienen sentido, porque un DeviceType puede tener muchos Software o Firmware (s), pero la propiedad de navegación inversa está definida en SoftwareFirmware. Entonces, ¿qué sucede cuando algo que no es un Firmware o Software tiene un DeviceType? Su inverso se configura como> DeviceType.AvailableSoftwareVersions pero eso no puede funcionar. Incluso sacando a EF de la imagen la forma correcta de modelar que es tener la propiedad Proyecto en Informe.

Eso fue con EF5. Si mi memoria es correcta y es el mismo problema, entonces tal vez no haya cambiado para EF6. Quizás deberíamos ver si hay un problema para resolver este problema. Sin embargo, su explicación adicional sugiere que no es un error sino una protección.

(Voy a hacerle un ping para verificar que estoy infiriendo el problema anterior a este correctamente).

En ese correo electrónico, Rowan también sugirió usar una lógica de obtención en lugar de propiedades de navegación como solución alternativa.



Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué