Note: there is a "better" iteration of this question here:
EF Code First migrations: Table Per Hierarchy Bug
I am trying to figure out the exact steps need to take an existing database and overlay entity framework code first with a Table Per Hierarchy structure. However no matter what I do, the end result migration is "wrong".
Here is the sample database, exact steps and output.
Existing database structure
CREATE TABLE [dbo].[Categories](
[Id] [int] IDENTITY(1,1) NOT NULL Primary Key,
[Name] [nvarchar](100) NOT NULL,
)
GO
CREATE TABLE [dbo].[A](
[Id] [int] IDENTITY(1,1) NOT NULL Primary Key,
[Discriminator] [nvarchar](20) NOT NULL,
[Description] [nvarchar](100) NOT NULL,
[CategoryId] [int] NULL
)GO
ALTER TABLE [dbo].[A]
WITH CHECK ADD CONSTRAINT [FK_dbo.A_dbo.Categories_CategoryId]
FOREIGN KEY([CategoryId])
REFERENCES [dbo].[Categories] ([Id])
GO
ALTER TABLE [dbo].[A]
CHECK CONSTRAINT [FK_dbo.A_dbo.Categories_CategoryId]
GO
"Reverse Engineer Code First" generates the following:
public partial class EntityContext : DbContext {
static EntityContext() {
Database.SetInitializer<EntityContext>(null);
}
public EntityContext()
: base("Name=Test47Context") {
}
public DbSet<A> A { get; set; }
public DbSet<Category> Categories { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
modelBuilder.Configurations.Add(new AMap());
modelBuilder.Configurations.Add(new CategoryMap());
}
}
public partial class A {
public int Id { get; set; }
public string Discriminator { get; set; }
public string Description { get; set; }
public Nullable<int> CategoryId { get; set; }
public virtual Category Category { get; set; }
}
public partial class Category {
public Category() {
this.A = new List<A>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<A> A { get; set; }
}
public class AMap : EntityTypeConfiguration<A> {
public AMap() {
// Primary Key
this.HasKey(t => t.Id);
// Properties
this.Property(t => t.Discriminator)
.IsRequired()
.HasMaxLength(20);
this.Property(t => t.Description)
.IsRequired()
.HasMaxLength(100);
// Table & Column Mappings
this.ToTable("A");
this.Property(t => t.Id).HasColumnName("Id");
this.Property(t => t.Discriminator).HasColumnName("Discriminator");
this.Property(t => t.Description).HasColumnName("Description");
this.Property(t => t.CategoryId).HasColumnName("CategoryId");
// Relationships
this.HasOptional(t => t.Category)
.WithMany(t => t.A)
.HasForeignKey(d => d.CategoryId);
}
}
public class CategoryMap : EntityTypeConfiguration<Category> {
public CategoryMap() {
// Primary Key
this.HasKey(t => t.Id);
// Properties
this.Property(t => t.Name)
.IsRequired()
.HasMaxLength(100);
// Table & Column Mappings
this.ToTable("Categories");
this.Property(t => t.Id).HasColumnName("Id");
this.Property(t => t.Name).HasColumnName("Name");
}
}
Implement TPH, by
- Adding
Derived.cs
inherits fromA.cs
- Moving
Category
andCategoryId
properties fromA.cs
toDerived.cs
- Remove
Discriminator
property fromA.cs
- In
AMap.cs
removeDiscriminator
,CategoryId
andCategory
Foreign Key mapping. Adding TPH mapping to
OnModelCreating
public partial class EntityContext : DbContext { static EntityContext() { Database.SetInitializer<EntityContext>(null); } public EntityContext() : base("Name=Test47Context") { } public DbSet<A> A { get; set; } public DbSet<Category> Categories { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Configurations.Add(new AMap()); modelBuilder.Configurations.Add(new CategoryMap()); modelBuilder.Entity<A>() .Map<Derived>(x => x.Requires("Discriminator").HasValue("Derived")); } } public partial class A { public int Id { get; set; } public string Description { get; set; } } public class Derived : A { public Nullable<int> CategoryId { get; set; } public virtual Category Category { get; set; } } public partial class Category { public Category() { this.A = new List<A>(); } public int Id { get; set; } public string Name { get; set; } public virtual ICollection<A> A { get; set; } } public class AMap : EntityTypeConfiguration<A> { public AMap() { // Primary Key this.HasKey(t => t.Id); this.Property(t => t.Description) .IsRequired() .HasMaxLength(100); // Table & Column Mappings this.ToTable("A"); this.Property(t => t.Id).HasColumnName("Id"); this.Property(t => t.Description).HasColumnName("Description"); } } public class CategoryMap : EntityTypeConfiguration<Category> { public CategoryMap() { // Primary Key this.HasKey(t => t.Id); // Properties this.Property(t => t.Name) .IsRequired() .HasMaxLength(100); // Table & Column Mappings this.ToTable("Categories"); this.Property(t => t.Id).HasColumnName("Id"); this.Property(t => t.Name).HasColumnName("Name"); } }
"Add-migration TPH" generates the following
public partial class TPH : DbMigration
{
public override void Up()
{
AddColumn("dbo.A", "Category_Id", c => c.Int());
AddForeignKey("dbo.A", "Category_Id", "dbo.Categories", "Id");
CreateIndex("dbo.A", "Category_Id");
DropColumn("dbo.A", "Discriminator");
}
public override void Down()
{
AddColumn("dbo.A", "Discriminator", c => c.String(nullable: false, maxLength: 20));
DropIndex("dbo.A", new[] { "Category_Id" });
DropForeignKey("dbo.A", "Category_Id", "dbo.Categories");
DropColumn("dbo.A", "Category_Id");
}
}
- Why does it drop the
Discriminator
column instead of just altering it tonvarchar(128)
? - Why doesn't just use the existing
CategoryId
column instead of addingCategory_Id
?