-
Notifications
You must be signed in to change notification settings - Fork 3.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Polymorphic relationship on base class #12867
Comments
I came up with this... But now I feel like I should be thrown in programmer jail for library abuse. public abstract class RootBase
{
protected RootBase()
{
}
public int Id { get;set; }
public string Name { get;set; }
public string DetailsType { get; protected set; }
[NotMapped]
public abstract DetailsBase Details { get; set; }
}
public abstract class Root<TDetails> : RootBase
where TDetails : Details<TDetails>
{
public Root()
{
}
protected internal virtual TDetails DetailsInternal { get; set; }
public override DetailsBase Details { get => DetailsInternal; set => DetailsInternal = (TDetails)value; }
}
public abstract DetailsBase
{
protected DetailsBase()
{
}
public int Id { get; set; }
}
public abstract Details<TSelf> : DetailsBase
where TSelf : Details<TSelf>
{
protected DetailsBase()
{
}
public virtual Root<TSelf> Root { get; set; }
}
public class UnCoolDetails : DetailsBase
{
public const string DetailsType = "uncool";
public UnCoolDetails()
{
}
public int UnCoolLevel { get; set; }
}
public class CoolDetails : DetailsBase
{
public const string DetailsType = "cool";
public CoolDetails()
{
}
public string CoolName { get; set; }
}
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options)
: base(options)
{
}
public DbSet<RootBase> Roots { get; set; }
public DbSet<CoolDetails> CoolDetails { get; set; }
public DbSet<UnCoolDetails> UnCoolDetails { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Configure<RootBase>(b => {
b.ToTable("Roots");
b.HasKey(e => e.Id);
b.HasIndee(e => e.Name)
.IsUnique();
b.HasDiscriminator(e => e.DetailsType)
.HasValue<Person<CoolDetails>>(CoolDetails.DetailsType)
.HasValue<Person<UnCoolDetails>>(UnCoolDetails.DetailsType);
});
modelBuilder.Configure<CoolDetails>(b => {
b.ToTable("CoolDetails");
b.HasKey(e => e.Id);
b.HasOne(e => e.Root)
.WithOne(e => e.DetailsInternal)
.HasForeignKey<Root<CoolDetails>>("CoolDetailsId");
});
modelBuilder.Configure<UnCoolDetails>(b => {
b.ToTable("UnCoolDetails");
b.HasKey(e => e.Id);
b.HasOne(e => e.Root)
.WithOne(e => e.DetailsInternal)
.HasForeignKey<Root<UnCoolDetails>>("UnCoolDetailsId");
});
}
}
using(var db = new DbContext())
{
db.Roots.Add(new Root<CoolDetails>
{
Name = "Config_1",
Details = new CoolDetails
{
CoolName = "Super Cool"
}
});
db.SaveChangesAsync();
}
using(var db = new DbContext())
{
var Roots = db.Roots.Include("DetailsInternal").First(x => x.Name == "Config_1");
//Roots.First().Details.GetType() == typeof(CoolDetails);
} Also it throws a null reference exception if you do |
@Mardoxx It's not clear to me exactly what you want the database mapping to be, or whether you need the generic types, etc. as opposed to them being included just to try to get it to work. Polymorphic relationships are not fully supported (#7623), but some things do work. Here's an example which, from what I can tell, does basically what you are asking for: public class Root
{
public int Id { get; set; }
public string Name { get; set; }
public virtual Details Details { get; set; }
}
public abstract class Details
{
public int Id { get; set; }
}
public class UnCoolDetails : Details
{
public int UnCoolLevel { get; set; }
}
public class CoolDetails : Details
{
public string CoolName { get; set; }
}
public class AppDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0");
public DbSet<Root> Roots { get; set; }
public DbSet<CoolDetails> CoolDetails { get; set; }
public DbSet<UnCoolDetails> UnCoolDetails { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<UnCoolDetails>()
.HasOne<Root>()
.WithOne(e => (UnCoolDetails)e.Details)
.HasForeignKey<Root>();
modelBuilder
.Entity<CoolDetails>()
.HasOne<Root>()
.WithOne(e => (CoolDetails)e.Details)
.HasForeignKey<Root>();
}
}
public class Program
{
public static void Main()
{
using (var context = new AppDbContext())
{
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
context.AddRange(
new Root
{
Details = new UnCoolDetails {UnCoolLevel = 77}
},
new Root
{
Details = new CoolDetails {CoolName = "Coolio"}
});
context.SaveChanges(false);
}
using (var context = new AppDbContext())
{
foreach (var root in context.Roots.Include(e => e.Details).ToList())
{
Console.WriteLine($"Root {root.Id} has {root.Details.GetType().Name} {root.Details.Id}");
}
}
}
} |
I want to have a collection of
Root
objects which have aDetails
property of some implementation of a common abstract base class.Something like this.
With ability to do
Is there a supported way I can do this?
Maybe what I want is something mid way between TPC and table splitting - where the common properties are kept in one table, extra properties per type are stored on separate tables.
What I want to avoid is doing the following:
Perhaps there's a better way to model this data?
The text was updated successfully, but these errors were encountered: