Fluent API - リレーションシップ

Note

このページでは、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 メソッドを使用して外部キーのプロパティを構成できます。 このメソッドは、外部キーとして使用されるプロパティを表すラムダ式を受け取ります。

必須対省略可能の関係を構成する (一対ゼロまたは一対一)

次の例では、一対ゼロまたは一対一のリレーションシップを構成します。 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);

両端を必須とするリレーションシップを構成する (一対一)

ほとんどの場合、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 規約を使用して結合テーブルを作成します。 その結果、Course_CourseID 列と Instructor_InstructorID 列を含む CourseInstructor テーブルが作成されます。

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 つのナビゲーション プロパティを使用してリレーションシップを構成する

リレーションシップの両端ではなくどちらか一方にのみナビゲーション プロパティが定義されている場合、単方向 (一方向) のリレーションシップとなります。 規約上、一方向リレーションシップは、Code First により常に一対多として解釈されます。 たとえば、Instructor と OfficeAssignment 間に一対一リレーションシップが必要で、ナビゲーション プロパティが 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 許容ではない場合、リレーションシップにカスケード削除が設定されます。 依存エンティティの外部キーが Null 許容の場合は、リレーションシップにカスケード削除は設定されません。プリンシパルが削除されたときは、外部キーが 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"));

Code First 規約に準拠しない外部キー名を構成する

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; }
}