2

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 from A.cs
  • Moving Category and CategoryId properties from A.cs to Derived.cs
  • Remove Discriminator property from A.cs
  • In AMap.cs remove Discriminator, CategoryId and Category 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 to nvarchar(128)?
  • Why doesn't just use the existing CategoryId column instead of adding Category_Id?
4

0 回答 0