注
このページでは、Fluent API を使用して Code First モデルのリレーションシップを設定する方法について説明します。 EF のリレーションシップに関する一般的な情報と、リレーションシップを使用してデータにアクセスして操作する方法については、「 リレーションシップとナビゲーションのプロパティ」を参照してください。
Code First を使用する場合は、ドメイン CLR クラスを定義してモデルを定義します。 既定では、Entity Framework は Code First 規則を使用してクラスをデータベース スキーマにマップします。 Code First の名前付け規則を使用する場合、ほとんどの場合、Code First に依存して、クラスで定義した外部キーとナビゲーション プロパティに基づいてテーブル間のリレーションシップを設定できます。 クラスを定義するときに規則に従わない場合、または規則の動作方法を変更する場合は、Fluent API またはデータ注釈を使用してクラスを構成し、Code First がテーブル間のリレーションシップをマップできるようにします。
はじめに
Fluent API とのリレーションシップを構成する場合は、EntityTypeConfiguration インスタンスから始めて、HasRequired、HasOptional、または HasMany メソッドを使用して、このエンティティが参加するリレーションシップの種類を指定します。 HasRequired メソッドと HasOptional メソッドは、参照ナビゲーション プロパティを表すラムダ式を受け取ります。 HasMany メソッドは、コレクション ナビゲーション プロパティを表すラムダ式を受け取ります。 その後、WithRequired、WithOptional、および WithMany メソッドを使用して、逆ナビゲーション プロパティを構成できます。 これらのメソッドには、引数を受け取らないオーバーロードがあり、一方向ナビゲーションでカーディナリティを指定するために使用できます。
次に、HasForeignKey メソッドを使用して外部キープロパティを構成できます。 このメソッドは、外部キーとして使用するプロパティを表すラムダ式を受け取ります。
必須と省略可能なリレーションシップの構成 (1 対 0 または 1)
次の例では、1 対 0 または 1 のリレーションシップを構成します。 OfficeAssignment には、主キーと外部キーである InstructorID プロパティがあります。これは、プロパティの名前が、HasKey メソッドを使用して主キーを構成する規則に従っていないためです。
// Configure the primary key for the OfficeAssignment
modelBuilder.Entity<OfficeAssignment>()
.HasKey(t => t.InstructorID);
// Map one-to-zero or one relationship
modelBuilder.Entity<OfficeAssignment>()
.HasRequired(t => t.Instructor)
.WithOptional(t => t.OfficeAssignment);
両端が必要なリレーションシップの構成 (1 対 1)
ほとんどの場合、Entity Framework では、どの型が依存していて、どの型がリレーションシップのプリンシパルであるかを推測できます。 ただし、リレーションシップの両端が必要な場合、または両側が省略可能な場合、Entity Framework は依存とプリンシパルを識別できません。 リレーションシップの両端が必要な場合は、HasRequired メソッドの後に WithRequiredPrincipal または WithRequiredDependent を使用します。 リレーションシップの両端が省略可能な場合は、HasOptional メソッドの後に WithOptionalPrincipal または WithOptionalDependent を使用します。
// Configure the primary key for the OfficeAssignment
modelBuilder.Entity<OfficeAssignment>()
.HasKey(t => t.InstructorID);
modelBuilder.Entity<Instructor>()
.HasRequired(t => t.OfficeAssignment)
.WithRequiredPrincipal(t => t.Instructor);
多対多リレーションシップの構成
次のコードでは、Course 型と Instructor 型の間に多対多リレーションシップを構成します。 次の例では、既定の Code First 規則を使用して結合テーブルを作成します。 その結果、CourseInstructor テーブルはCourse_CourseID列とInstructor_InstructorID列で作成されます。
modelBuilder.Entity<Course>()
.HasMany(t => t.Instructors)
.WithMany(t => t.Courses)
結合テーブル名とテーブル内の列の名前を指定する場合は、Map メソッドを使用して追加の構成を行う必要があります。 次のコードでは、CourseID 列と InstructorID 列を含む CourseInstructor テーブルが生成されます。
modelBuilder.Entity<Course>()
.HasMany(t => t.Instructors)
.WithMany(t => t.Courses)
.Map(m =>
{
m.ToTable("CourseInstructor");
m.MapLeftKey("CourseID");
m.MapRightKey("InstructorID");
});
1 つのナビゲーション プロパティを使用したリレーションシップの構成
一方向 (単一方向) リレーションシップは、ナビゲーション プロパティがリレーションシップの 1 つだけで定義され、両方で定義されていない場合です。 慣例により、Code First は常に一方向のリレーションシップを一対多として解釈します。 たとえば、Instructor と OfficeAssignment の間に 1 対 1 のリレーションシップが必要な場合、Instructor 型のみにナビゲーション プロパティがある場合は、fluent API を使用してこのリレーションシップを構成する必要があります。
// Configure the primary Key for the OfficeAssignment
modelBuilder.Entity<OfficeAssignment>()
.HasKey(t => t.InstructorID);
modelBuilder.Entity<Instructor>()
.HasRequired(t => t.OfficeAssignment)
.WithRequiredPrincipal();
連鎖削除の有効化
WillCascadeOnDelete メソッドを使用して、リレーションシップの連鎖削除を構成できます。 依存エンティティの外部キーが null 許容でない場合、Code First はリレーションシップに連鎖削除を設定します。 依存エンティティの外部キーが null 許容の場合、Code First はリレーションシップに連鎖削除を設定せず、プリンシパルが削除されると外部キーは null に設定されます。
次を使用して、これらの連鎖削除規則を削除できます。
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>()
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>()
次のコードでは、リレーションシップを必須に構成し、連鎖削除を無効にします。
modelBuilder.Entity<Course>()
.HasRequired(t => t.Department)
.WithMany(t => t.Courses)
.HasForeignKey(d => d.DepartmentID)
.WillCascadeOnDelete(false);
複合外部キーの構成
Department 型の主キーが DepartmentID プロパティと Name プロパティで構成されている場合は、次のように Department の主キーと Course タイプの外部キーを構成します。
// Composite primary key
modelBuilder.Entity<Department>()
.HasKey(d => new { d.DepartmentID, d.Name });
// Composite foreign key
modelBuilder.Entity<Course>()
.HasRequired(c => c.Department)
.WithMany(d => d.Courses)
.HasForeignKey(d => new { d.DepartmentID, d.DepartmentName });
モデルで定義されていない外部キーの名前変更
CLR 型に外部キーを定義せず、データベースに必要な名前を指定する場合は、次の操作を行います。
modelBuilder.Entity<Course>()
.HasRequired(c => c.Department)
.WithMany(t => t.Courses)
.Map(m => m.MapKey("ChangedDepartmentID"));
コードの最初の規則に従わない外部キー名の構成
Course クラスの外部キー プロパティが DepartmentID ではなく SomeDepartmentID と呼ばれる場合、SomeDepartmentID を外部キーにすることを指定するには、次の操作を行う必要があります。
modelBuilder.Entity<Course>()
.HasRequired(c => c.Department)
.WithMany(d => d.Courses)
.HasForeignKey(c => c.SomeDepartmentID);
サンプルで使用されるモデル
このページのサンプルでは、次の Code First モデルを使用します。
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;
// add a reference to System.ComponentModel.DataAnnotations DLL
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
using System;
public class SchoolEntities : DbContext
{
public DbSet<Course> Courses { get; set; }
public DbSet<Department> Departments { get; set; }
public DbSet<Instructor> Instructors { get; set; }
public DbSet<OfficeAssignment> OfficeAssignments { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Configure Code First to ignore PluralizingTableName convention
// If you keep this convention then the generated tables will have pluralized names.
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
public class Department
{
public Department()
{
this.Courses = new HashSet<Course>();
}
// Primary key
public int DepartmentID { get; set; }
public string Name { get; set; }
public decimal Budget { get; set; }
public System.DateTime StartDate { get; set; }
public int? Administrator { get; set; }
// Navigation property
public virtual ICollection<Course> Courses { get; private set; }
}
public class Course
{
public Course()
{
this.Instructors = new HashSet<Instructor>();
}
// Primary key
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
// Foreign key
public int DepartmentID { get; set; }
// Navigation properties
public virtual Department Department { get; set; }
public virtual ICollection<Instructor> Instructors { get; private set; }
}
public partial class OnlineCourse : Course
{
public string URL { get; set; }
}
public partial class OnsiteCourse : Course
{
public OnsiteCourse()
{
Details = new Details();
}
public Details Details { get; set; }
}
public class Details
{
public System.DateTime Time { get; set; }
public string Location { get; set; }
public string Days { get; set; }
}
public class Instructor
{
public Instructor()
{
this.Courses = new List<Course>();
}
// Primary key
public int InstructorID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public System.DateTime HireDate { get; set; }
// Navigation properties
public virtual ICollection<Course> Courses { get; private set; }
}
public class OfficeAssignment
{
// Specifying InstructorID as a primary
[Key()]
public Int32 InstructorID { get; set; }
public string Location { get; set; }
// When Entity Framework sees Timestamp attribute
// it configures ConcurrencyCheck and DatabaseGeneratedPattern=Computed.
[Timestamp]
public Byte[] Timestamp { get; set; }
// Navigation property
public virtual Instructor Instructor { get; set; }
}
.NET