Reputation: 3806
I have an Entity Framework 6 (v6.5.1) code-first model like this (type names have been changed to anonymise the code):
public abstract class Point
{
public long ID { get; set; }
public long PointSetID { get; set; }
public PointSet PointSet { get; set; }
public double X { get; set; }
public double Y { get; set; }
public double Z { get; set; }
}
public abstract class DetailedPoint : Point
{
public String Description { get; set; }
public Byte[] Data { get; set; }
}
public class Category1Point : Point
{
}
public class Category2Point: DetailedPoint
{
}
public class Category3Point : DetailedPoint
{
}
public class PointSet
{
public PointSet()
{
Category1Points = new HashSet<Category1Point>();
Category2Points = new HashSet<Category2Point>();
Category3Points = new HashSet<Category3Point>();
}
public long ID { get; set; }
public virtual ICollection<Point> Points { get; set; }
public virtual ICollection<Category1Point> Category1Points { get; set; }
public virtual ICollection<Category2Point> Category2Points { get; set; }
public virtual ICollection<Category3Point> Category3Points { get; set; }
}
public class PointContext : DbContext
{
public virtual DbSet<Point> Points { get; set; }
public virtual DbSet<PointSet> PointSets { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<PointSet>()
.HasMany(e => e.Points)
.WithRequired(e => e.PointSet)
.HasForeignKey(e => e.PointSetID)
.WillCascadeOnDelete(true);
}
}
The three categories of Point have the same data model but are not interchangeable because they are measurements of different kinds of thing, which is why they have different concrete classes. The
When I run Add-Migration
, the relationships between Point
and PointSet
are scaffolded in a way that looks pretty ugly. It's created three additional columns for extra foreign key relationships, with separate indexes. These ought to be redundant since the base Point
class table already has a foreign key relationship to the PointSet
.
CreateTable(
"dbo.PointSets",
c => new
{
ID = c.Long(nullable: false, identity: true),
})
.PrimaryKey(t => t.ID);
CreateTable(
"dbo.Points",
c => new
{
ID = c.Long(nullable: false, identity: true),
PointSetID = c.Long(nullable: false),
X = c.Double(nullable: false),
Y = c.Double(nullable: false),
Z = c.Double(nullable: false),
Description = c.String(maxLength: 500),
Data = c.Binary(),
Discriminator = c.String(nullable: false, maxLength: 128),
PointSet_ID = c.Long(),
PointSet_ID1 = c.Long(),
PointSet_ID2 = c.Long(),
})
.PrimaryKey(t => t.ID)
.ForeignKey("dbo.PointSets", t => t.PointSet_ID)
.ForeignKey("dbo.PointSets", t => t.PointSet_ID1)
.ForeignKey("dbo.PointSets", t => t.PointSet_ID2)
.ForeignKey("dbo.PointSets", t => t.PointSetID, cascadeDelete: true)
.Index(t => t.PointSetID)
.Index(t => t.PointSet_ID)
.Index(t => t.PointSet_ID1)
.Index(t => t.PointSet_ID2);
I tried to use OnModelCreating
to force the CategoryXPoints
navigation properties to use the existing PointSetID
field as a foreign key, like this:
modelBuilder.Entity<PointSet>()
.HasMany(e => e.Points)
.WithRequired(e => e.PointSet)
.HasForeignKey(e => e.PointSetID)
.WillCascadeOnDelete(true);
modelBuilder.Entity<PointSet>()
.HasMany(e => e.Category1Points)
.WithRequired(e => e.PointSet)
.HasForeignKey(e => e.PointSetID)
.WillCascadeOnDelete(false);
modelBuilder.Entity<PointSet>()
.HasMany(e => e.Category2Points)
.WithRequired(e => e.PointSet)
.HasForeignKey(e => e.PointSetID)
.WillCascadeOnDelete(false);
modelBuilder.Entity<PointSet>()
.HasMany(e => e.Category3Points)
.WithRequired(e => e.PointSet)
.HasForeignKey(e => e.PointSetID)
.WillCascadeOnDelete(false);
But when I try that, Add-Migration
throws this exception:
System.InvalidOperationException: The foreign key component 'PointSetID' is not a declared property on type 'Category1Point'. Verify that it has not been explicitly excluded from the model and that it is a valid primitive property.
I don't understand why this exception is thrown, since the class absolutely does have a PointSetID
property which is a valid primitive, and I have definitely not explicitly excluded it from the model.
If I delete the .HasForeignKey(e => e.PointSetID)
calls in OnModelBuilder()
, I get the following error instead:
PointSet: FromRole: NavigationProperty 'PointSet' is not valid. Type 'Category3Point' of FromRole 'PointSet_Category3Points_Target' in AssociationType 'PointSet_Category3Points' must exactly match with the type 'Point' on which this NavigationProperty is declared on.
Is it possible to set up navigation properties from one class to different individual sub-types in a TPH table without creating a bunch of redundant foreign keys and indexes?
Upvotes: 0
Views: 21