Is it possible to have multiple collection properties of entities with the same base class (TPH) inside one entity?

c# entity-framework-core

Question

In my project I use Entity Framework core 2.0. The next code is slightly simplified. There are such models:

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
{
}

I use Fluent API to configure relationships:

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

It look's like the migration and database are created correctly. But when I create new Site object:

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

the next exception is thrown "Unable to cast object of type 'ApplicationCore.Entities.AudioLink' to type 'ApplicationCore.Entities.VideoLink'."
Could you tell what am I doing wrong?

1
1
5/6/2018 9:07:13 PM

Accepted Answer

Inheritance does not change the rule that single reference navigation property can be mapped only to a single inverse collection navigation property.

In fact what is happening is that the second HasMany / WithOne overrides the previous one (probably a bug, should throw exception), so the Link.Site is mapped to the Site.AudioLinks (not that the Site is a property of the base entity Site, hence is shared by AudioLink and VideoLink).

So you either have to remove the Site and SiteId properties from the base class and put them in the derived classes (which will introduce 2 FK relationships), or better use a single collection navigation property of the base type:

public class Site
{
    public int Id { get; set; } 
    public string Name { get; set; }
    public ICollection<Link> Links { get; set; }
}

and mapping:

builder.Entity<Site>().HasMany(s => s.Links)
   .WithOne(l => l.Site)
   .HasForeignKey(l => l.SiteId);

You can always use the OfType() operator to get the AudioLinks or VideoLinks from Links in both LINQ to Entities query or LINQ to Objects after Site materialization.

4
5/6/2018 9:17:05 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow