はじめに
現在、とあるプロジェクトでEntity Framework6を使用していています。
Entity Frameworkはなかなか便利なのですが、他のORマッパーと比較し、ちょいちょい独特な点があります。
(特に、最近までLaravelを中心に開発していたので、かなり使いにくく感じる点はあります)
特に厄介なのは、リレーション周り。
リレーションは、以下のように構成します。
ContextのOnModelCreatingに、リレーションを一通り書くことになります。
ここで、1:nのリレーションの書き方で、かなり迷うことになりました。
いまだにEFCoreではなく、EF6を使用している方は少ないかもしれませんが、ここではそんな希少価値の高い方々向けに、
リレーション周りについて、まとめていくことにしました。
例のテーブル構成
CREATE TABLE [dbo].[テーブル11](
[主キー] [int] PRIMARY KEY NOT NULL,
[値] [nvarchar](128) NULL
);
CREATE TABLE [dbo].[テーブル12](
[主キー] [int] PRIMARY KEY NOT NULL,
[外部キー] [int] NOT NULL,
[値] [nvarchar](128) NULL
);
CREATE TABLE [dbo].[テーブル13](
[主キー] [int] PRIMARY KEY NOT NULL,
[外部キー] [int] NULL,
[値] [nvarchar](128) NULL
);
CREATE TABLE [dbo].[テーブル31](
[主キー] [int] PRIMARY KEY NOT NULL,
[値] [nvarchar](128) NULL
);
CREATE TABLE [dbo].[テーブル32](
[主キー] [int] NOT NULL,
[連番] [int] NOT NULL,
[値] [nvarchar](128) NULL,
primary key (主キー, 連番)
);
ER図
そのうち
HasRequiredとHasOptionalの使い分け
1:nリレーションを構築する際に、HasRequiredもしくはHasOptionalというメソッドを使用します。
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Table12>() // 子テーブル
.HasRequired(x => x.Table11) // 親テーブルへの参照
.WithMany(x => x.Table12s) // 子テーブルへの参照
.HasForeignKey(x => new { x.Gaibuki }); // 子テーブル側の、外部キーの設定
modelBuilder.Entity<Table23>() // 子テーブル
.HasOptional(x => x.Table21) // 親テーブルへの参照
.WithMany(x => x.Table23s) // 子テーブルへの参照
.HasForeignKey(x => new { x.Gaibuki }); // 子テーブル側の、外部キーの設定
}
このメソッドの使い分けについて、記載していきます。
HasRequired:そのキーが必須の場合 HasOptional:そのキーが必須でない場合
1:nのリレーションを行う場合でも、以下のようなケースがあると思います。
-
必ず、1:nの親子関係が組まれる場合。例:「契約」と「契約明細」という1:nテーブルがある場合、「契約」の外部キーをNULLとして「契約明細」のデータが作成されることは無い(契約明細のみ単独で作ることは無い)ので、「契約」への外部キーは必須となる。
-
1:nの親子関係が組まれないケースがある場合。例:「契約」というテーブルに「削除ユーザー」という列があり、削除ユーザーのIDを入れることによって、「ユーザー」テーブルへのリレーションが行われる場合。削除されている場合のみidをセットするため、必須ではない。
このように、「nは、1へのリレーションが必ず行われる場合」と「nは、1へのリレーションが無くてもデータ作成が可能な場合」の2種類あることが分かります。
この使い分けが、HasRequiredとHasOptionalの使い分けです。
制約
HasRequiredを使用する場合
・外部キーに設定した列は、null非許容型である必要がある
→int?のようなnull許容型は使用できません。なぜなら、親テーブルへの参照が必須となるためです。
よって、intのようなnull非許容型である必要があります。
HasOptionalを使用する場合
・外部キーに設定した列は、null許容型である必要がある?
→int?のようなnull許容型が使用できます。また、ここは要調査なのですが、intのようなnull非許容型も、使用できるかもしれません。
・外部キーに設定した列に、Required属性は設定できない
→外部キーに設定した列に、Required属性は設定できないようです。
例
以下のようなModel構成があったとします。
テーブル12は必須リレーション、テーブル13は任意リレーションです。
/// <summary>
/// テーブル11
/// </summary>
[Table("テーブル11")]
[Serializable]
public partial class Table11
{
/// <summary>
/// 主キー
/// </summary>
[Column("主キー")]
[Display(Name = "主キー")]
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int? Syuki { get; set; }
/// <summary>
/// 値
/// </summary>
[Column("値")]
[Display(Name = "値")]
public string Atai { get; set; }
public virtual ICollection<Table12> Table12s { get; set; }
public virtual ICollection<Table13> Table13s { get; set; }
}
/// <summary>
/// テーブル12
/// </summary>
[Table("テーブル12")]
[Serializable]
public partial class Table12
{
/// <summary>
/// 主キー
/// </summary>
[Column("主キー")]
[Display(Name = "主キー")]
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int? Syuki { get; set; }
/// <summary>
/// 外部キー
/// </summary>
[Column("外部キー")]
[Display(Name = "外部キー")]
public int Gaibuki { get; set; }
/// <summary>
/// 値
/// </summary>
[Column("値")]
[Display(Name = "値")]
public string Atai { get; set; }
public virtual Table11 Table11 { get; set; }
}
/// <summary>
/// テーブル13
/// </summary>
[Table("テーブル13")]
[Serializable]
public partial class Table13
{
/// <summary>
/// 主キー
/// </summary>
[Column("主キー")]
[Display(Name = "主キー")]
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int? Syuki { get; set; }
/// <summary>
/// 外部キー(null許可)
/// </summary>
[Column("外部キー")]
[Display(Name = "外部キー")]
public int? Gaibuki { get; set; }
/// <summary>
/// 値
/// </summary>
[Column("値")]
[Display(Name = "値")]
public string Atai { get; set; }
public virtual Table11 Table11 { get; set; }
}
この場合、以下のようなリレーション設定となります。
テーブル12は必須リレーションなのでHasOptionalはOK、テーブル13は任意リレーションなのでHasOptionalはOKです。
// OK
modelBuilder.Entity<Table12>()
.HasRequired(x => x.Table11)
.WithMany(x => x.Table12s)
.HasForeignKey(x => new { x.Gaibuki });
// ダメ
//modelBuilder.Entity<Table12>()
// .HasOptional(x => x.Table11)
// .WithMany(x => x.Table12s)
// .HasForeignKey(x => new { x.Gaibuki });
// OK
modelBuilder.Entity<Table13>()
.HasOptional(x => x.Table11)
.WithMany(x => x.Table13s)
.HasForeignKey(x => new { x.Gaibuki });
// これもOK
//modelBuilder.Entity<Table13>()
// .HasRequired(x => x.Table11)
// .WithMany(x => x.Table13s)
// .HasForeignKey(x => new { x.Gaibuki });
1:nのn側のテーブルが、複合キーの場合は、HasRequired
1:nのn側のテーブルで、以下のような複合主キー設定が行われている場合があります。今回の例でいうと、テーブル32が該当します。
- 1側のテーブルの主キー
- 連番
この場合、外部キーは「1側のテーブルの主キー」になります。
そしてこの場合、この主キーには値が必ず入ってくるので、HasRequiredとなります。
その他
細かいことはまた後ほど!