- 多くの場合、既存のデータベースで Entity Framework Code First を使用する必要があります。
- 既存のデータベースには、「階層ごとのテーブル」継承を許可する構造がある場合があります。
- または、次のようなオブジェクト モデルから始めることもできます。
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; }
}
最初の移行を作成します。
enable-migrations
add-migration Initial
移行は次のようになります。
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);
}
このデータベースを生成するには:
update-database
これにより、このように生成できるデータベースが得られます。
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)
このデータベースを本番環境でしばらく使用しています...
ここで、一般の人とは異なる学生という概念を追加したいと考えています。
Entity Framework には、継承を表すための 3 つのアプローチが用意されています。この場合、「階層ごとのテーブル」アプローチを選択します。
このアプローチを実装するために、POCO を次のように変更します。
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; }
}
ノート:
- 学生のみがプロパティにアクセスでき
StudentType
ます。 - クラスでは
Discriminator
プロパティを指定しません。Person
EF Code First は、Person テーブルStudent
から継承されPerson
た列を確認し、列を追加します。Discriminator
次に実行します。
add-migration Person_TPH
そして、この予期しない出力が得られます。
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");
}
StudentType_Id
列またはインデックスを追加するべきではありません。
「StudentMap」クラスを追加することで明示的にできます。
public class StudentMap : EntityTypeConfiguration<Student> {
public StudentMap() {
this.HasOptional(x => x.StudentType)
.WithMany()
.HasForeignKey(x => x.StudentTypeId);
}
}
しかし、喜びはありません..
実際、データベースとすべての移行を削除すると. 次にadd-migration Initial
、新しいモデルに対して実行すると、次のようになります。
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);
}
この「正しい」バージョンでは、EF Code First の移行StudentTypeId
で期待どおりに列が使用されていることがわかります。
質問
データベースが既に存在する場合、EF Code First の移行で既存のStudentTypeId
列を使用するように指示する方法はありますか。
問題を示す GitHub リポジトリは次のとおりです。
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