问题
- Often we might need to use Entity Framework Code First with an existing database.
- The existing database may have a structure the allows "Table Per Hierarchy" inheritance.
- Or we might start with an object model that looks like:
public partial class Person {
public int Id { get; set; }
public string Discriminator { get; set; }
public string Name { get; set; }
public Nullable<int> StudentTypeId { get; set; }
public virtual StudentType StudentType { get; set; }
}
public partial class StudentType {
public StudentType() {
this.People = new List<Person>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Person> People { get; set; }
}
We create the initial migration:
enable-migrations
add-migration Initial
The migration looks like:
public override void Up()
{
CreateTable(
"dbo.Person",
c => new
{
Id = c.Int(nullable: false, identity: true),
Discriminator = c.String(maxLength: 4000),
Name = c.String(maxLength: 4000),
StudentTypeId = c.Int(),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.StudentType", t => t.StudentTypeId)
.Index(t => t.StudentTypeId);
CreateTable(
"dbo.StudentType",
c => new
{
Id = c.Int(nullable: false, identity: true),
Name = c.String(maxLength: 4000),
})
.PrimaryKey(t => t.Id);
}
To generate this database we:
update-database
This results in a database that we could have generated like this.
create table Person(
Id int Identity(1,1) Primary key,
Discriminator nvarchar(4000) null,
StudentTypeId int null,
)
create table StudentType(
Id int Identity(1,1) Primary key,
Name nvarchar(4000) not null
)
alter table Person
add constraint StudentType_Person
foreign key (StudentTypeId)
references StudentType(Id)
We use this database in production for a while...
Now we want to add the concept of students that are different from just regular people.
Entity Framework provides three approaches for representing inheritance. In this case we choose the "Table Per Hierarchy" approach.
To implement this approach we modify our POCOs as follows:
public class Person {
public int Id { Get; set; }
public string Name { get; set }
}
public class Student : Person {
public virtual StudentType StudentType { get; set; }
public int? StudentTypeId { get; set; }
}
public class StudentType {
public StudentType() {
Students = new List<Student>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Student> Students { get; set; }
}
Note:
- Only Students have access to the
StudentType
property. - We don't specify the
Discriminator
property in ourPerson
class. EF Code First sees thatStudent
inherits fromPerson
and will add aDiscriminator
column to the Person table for us.
Now we run:
add-migration Person_TPH
And we get this unexpected output.
public override void Up()
{
AddColumn("dbo.Person", "StudentType_Id", c => c.Int());
AlterColumn("dbo.Person", "Discriminator", c => c.String(nullable: false, maxLength: 128));
AddForeignKey("dbo.Person", "StudentType_Id", "dbo.StudentType", "Id");
CreateIndex("dbo.Person", "StudentType_Id");
}
It should not be adding the StudentType_Id
column or index.
We can be explicit by adding the 'StudentMap' class:
public class StudentMap : EntityTypeConfiguration<Student> {
public StudentMap() {
this.HasOptional(x => x.StudentType)
.WithMany()
.HasForeignKey(x => x.StudentTypeId);
}
}
But no joy..
Indeed, if we delete the database and all the migrations.
Then run add-migration Initial
against our new model we get:
public override void Up()
{
CreateTable(
"dbo.Person",
c => new
{
Id = c.Int(nullable: false, identity: true),
Name = c.String(maxLength: 4000),
StudentTypeId = c.Int(),
Discriminator = c.String(nullable: false, maxLength: 128),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.StudentType", t => t.StudentTypeId)
.Index(t => t.StudentTypeId);
CreateTable(
"dbo.StudentType",
c => new
{
Id = c.Int(nullable: false, identity: true),
Name = c.String(nullable: false, maxLength: 100),
})
.PrimaryKey(t => t.Id);
}
In this "correct" version we see that EF Code First migrations uses the StudentTypeId
column as expected.
Question
Given that the database already exists, is there a way to tell EF Code First migrations to use the existing StudentTypeId
column.
The GitHub repo that demonstrates the problem is here:
https://github.com/paulyk/ef_code_first_proof_of_tph_bug.git
Git tags
1_add_migration_Initial
2_add_migration_person_TPH
3_add_studentMap
回答1:
There are 3 conventions that I found that relate to the discovery of explicit foreign keys in the class:
System.Data.Entity.ModelConfiguration.Conventions.NavigationPropertyNameForeignKeyDiscoveryConvention System.Data.Entity.ModelConfiguration.Conventions.PrimaryKeyNameForeignKeyDiscoveryConvention System.Data.Entity.ModelConfiguration.Conventions.TypeNameForeignKeyDiscoveryConvention
The PrimaryKeyNameForeignKeyDiscoveryConvention
would not help here since the primary key on StudentType
is just Id
. The other two would both match on StudentTypeId
though, so as long as you aren't removing both of those, the conventions should pick it up.
According to this question (Foreign key navigation property naming convention alternatives) though, you can also add [ForeignKey("StudentTypeId")]
to the StudentType
property on Student
and [InverseProperty("StudentType")]
to the Students
property on StudentType
.
Hope that helps. :)
来源:https://stackoverflow.com/questions/16274653/ef-code-first-migrations-table-per-hierarchy-bug