Nel mio progetto utilizzo Entity Framework core 2.0. Il prossimo codice è leggermente semplificato. Ci sono tali modelli:
public class Site
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<AudioLink> AudioLinks { get; set; }
public ICollection<VideoLink> VideoLinks { get; set; }
}
public abstract class Link
{
public int Id { get; set; }
public string Href { get; set; }
public int SiteId { get; set; }
public Site Site { get; set; }
}
public class AudioLink : Link
{
}
public class VideoLink : Link
{
}
Uso Fluent API per configurare le relazioni:
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
public DbSet<Site> Sites { get; set; }
public DbSet<AudioLink> AudioLinks { get; set; }
public DbSet<VideoLink> VideoLinks { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Link>()
.HasDiscriminator<byte>("LinkType")
.HasValue<AudioLink>(1)
.HasValue<VideoLink>(2);
builder.Entity<Site >().HasMany(s => s.VideoLinks)
.WithOne(l => l.Site)
.HasForeignKey(l => l.SiteId);
builder.Entity<Site >().HasMany(s => s.AudioLinks)
.WithOne(l => l.Site)
.HasForeignKey(l => l.SiteId);
}
}
Sembra che la migrazione e il database siano stati creati correttamente. Ma quando creo un nuovo oggetto del sito:
var site = new Site(){Id = 1, Name = "SiteA"}
var audioLink = new AudioLink(){Id = 1, Href = "abc", Site = site};
context.Sites.Add(site);
context.SaveChanges();
context.AudioLinks.Add(audioLink);// exception occurs
context.SaveChanges();
viene lanciata l'eccezione successiva "Impossibile lanciare l'oggetto di tipo 'ApplicationCore.Entities.AudioLink' per digitare 'ApplicationCore.Entities.VideoLink'."
Potresti dire cosa sto sbagliando?
L'ereditarietà non modifica la regola che la proprietà di navigazione di un singolo riferimento può essere mappata solo a una singola proprietà di navigazione della raccolta inversa.
In realtà, ciò che sta accadendo è che il secondo HasMany
/ WithOne
sovrascrive quello precedente (probabilmente un bug, dovrebbe generare un'eccezione), quindi Link.Site
è mappato sul Site.AudioLinks
(non che il Site
sia una proprietà dell'entità di base Site
, quindi condiviso da AudioLink
e VideoLink
).
Pertanto, è necessario rimuovere le proprietà Site
e SiteId
dalla classe base e inserirle nelle classi derivate (che introdurranno 2 relazioni FK), oppure utilizzare una proprietà di navigazione raccolta singola del tipo base:
public class Site
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Link> Links { get; set; }
}
e mappatura:
builder.Entity<Site>().HasMany(s => s.Links)
.WithOne(l => l.Site)
.HasForeignKey(l => l.SiteId);
È sempre possibile utilizzare OfType()
operatore per ottenere i AudioLinks
o VideoLinks
da Links
in entrambe le query LINQ to Entities o LINQ to Objects dopo Site
materializzazione.