次の方法で共有


Fluent API - プロパティと型の構成とマッピング

Entity Framework Code を使用する場合、既定の動作は、EF に組み込まれた一連の規則を使用して POCO クラスをテーブルにマップすることです。 ただし、これらの規則に従いたくない場合や、エンティティを規則に規定されているもの以外にマップする必要がある場合もあります。

規則以外のものを使用するように EF を構成するには、 主に注釈 と EFs fluent API の 2 つの方法があります。 注釈は fluent API 機能のサブセットのみを対象とするため、注釈を使用して実現できないマッピング シナリオがあります。 この記事は、fluent API を使用してプロパティを構成する方法を示すために設計されています。

コードファーストの流暢なAPIには、通常、派生したDbContextOnModelCreatingメソッドをオーバーライドすることによってアクセスします。 次のサンプルは、fluent API を使用してさまざまなタスクを実行する方法を示し、コードをコピーしてモデルに合わせてカスタマイズできるように設計されています。as-is で使用できるモデルを確認する場合は、この記事の最後に示します。

モデル全体の設定

既定のスキーマ (EF6 以降)

EF6 以降では、DbModelBuilder の HasDefaultSchema メソッドを使用して、すべてのテーブルやストアド プロシージャなどに使用するデータベース スキーマを指定できます。この既定の設定は、別のスキーマを明示的に構成するすべてのオブジェクトに対してオーバーライドされます。

modelBuilder.HasDefaultSchema("sales");

カスタム規則 (EF6 以降)

EF6 以降では、Code First に含まれる規則を補完する独自の規則を作成できます。 詳細については、「 カスタム コードの最初の規則」を参照してください。

プロパティ マッピング

Property メソッドは、エンティティまたは複合型に属する各プロパティの属性を構成するために使用されます。 Property メソッドは、特定のプロパティの構成オブジェクトを取得するために使用されます。 構成オブジェクトのオプションは、構成されている型に固有です。IsUnicode は、たとえば文字列プロパティでのみ使用できます。

主キーの構成

主キーの Entity Framework 規則は次のとおりです。

  1. クラスは、名前が "ID" または "Id" のプロパティを定義します
  2. またはクラス名の後に "ID" または "Id" が続く

プロパティを主キーに明示的に設定するには、HasKey メソッドを使用します。 次の例では、HasKey メソッドを使用して、OfficeAssignment 型の InstructorID 主キーを構成します。

modelBuilder.Entity<OfficeAssignment>().HasKey(t => t.InstructorID);

複合主キーの構成

次の例では、DepartmentID プロパティと Name プロパティを Department 型の複合主キーとして構成します。

modelBuilder.Entity<Department>().HasKey(t => new { t.DepartmentID, t.Name });

数値主キーの ID をオフにする

次の例では、DepartmentID プロパティを System.ComponentModel.DataAnnotations.DatabaseGeneratedOption.None に設定して、値がデータベースによって生成されないことを示します。

modelBuilder.Entity<Department>().Property(t => t.DepartmentID)
    .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);

プロパティの最大長の指定

次の例では、Name プロパティは 50 文字以内にする必要があります。 値を 50 文字より長くすると、 DbEntityValidationException 例外が発生します。 Code First がこのモデルからデータベースを作成する場合、Name 列の最大長も 50 文字に設定されます。

modelBuilder.Entity<Department>().Property(t => t.Name).HasMaxLength(50);

プロパティを必須にする構成

次の例では、Name プロパティが必要です。 名前を指定しない場合は、DbEntityValidationException 例外が発生します。 Code First がこのモデルからデータベースを作成する場合、通常、このプロパティの格納に使用される列は null 非許容になります。

場合によっては、プロパティが必要であっても、データベース内の列を null 非許容にできない場合があります。 たとえば、複数の型に対して TPH 継承戦略データを使用する場合は、1 つのテーブルに格納されます。 派生型に必須プロパティが含まれている場合、階層内のすべての型がこのプロパティを持つわけではないため、列を null 非許容にすることはできません。

modelBuilder.Entity<Department>().Property(t => t.Name).IsRequired();

1 つ以上のプロパティに対するインデックスの構成

EF6.1 以降のみ - Index 属性は Entity Framework 6.1 で導入されました。 以前のバージョンを使用している場合、このセクションの情報は適用されません。

インデックスの作成は Fluent API ではネイティブにサポートされていませんが、Fluent API を使用して IndexAttribute のサポートを利用できます。 インデックス属性は、モデルの注釈をモデルに含めることで処理され、後でパイプライン内のデータベースのインデックスに変換されます。 Fluent API を使用して、これらの同じ注釈を手動で追加できます。

これを行う最も簡単な方法は、新しいインデックスのすべての設定を含む IndexAttribute のインスタンスを作成することです。 その後、IndexAttribute 設定を EF モデルに格納できるモデル注釈に変換する EF 固有の型である IndexAnnotation のインスタンスを作成できます。 その後、注釈の名前 Index を指定して、Fluent API の HasColumnAnnotation メソッドに渡すことができます。

modelBuilder
    .Entity<Department>()
    .Property(t => t.Name)
    .HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute()));

IndexAttribute で使用できる設定の完全な一覧については、「コード優先データ注釈」の「インデックス」セクションを参照してください。 これには、インデックス名のカスタマイズ、一意のインデックスの作成、複数列インデックスの作成が含まれます。

IndexAttribute の配列を IndexAnnotation のコンストラクターに渡すことで、1 つのプロパティに対して複数のインデックス注釈を指定できます。

modelBuilder
    .Entity<Department>()
    .Property(t => t.Name)
    .HasColumnAnnotation(
        "Index",  
        new IndexAnnotation(new[]
            {
                new IndexAttribute("Index1"),
                new IndexAttribute("Index2") { IsUnique = true }
            })));

データベース内の列に CLR プロパティをマップしないように指定する

次の例は、CLR 型のプロパティがデータベース内の列にマップされないように指定する方法を示しています。

modelBuilder.Entity<Department>().Ignore(t => t.Budget);

データベース内の特定の列への CLR プロパティのマッピング

次の例では、Clr プロパティの名前を DepartmentName データベース列にマップします。

modelBuilder.Entity<Department>()
    .Property(t => t.Name)
    .HasColumnName("DepartmentName");

モデルで定義されていない外部キーの名前変更

CLR 型に外部キーを定義せず、データベースに含める名前を指定する場合は、次の操作を行います。

modelBuilder.Entity<Course>()
    .HasRequired(c => c.Department)
    .WithMany(t => t.Courses)
    .Map(m => m.MapKey("ChangedDepartmentID"));

文字列プロパティが Unicode コンテンツをサポートするかどうかを構成する

既定では、文字列は Unicode (SQL Server では nvarchar) です。 IsUnicode メソッドを使用して、文字列が varchar 型であることを指定できます。

modelBuilder.Entity<Department>()
    .Property(t => t.Name)
    .IsUnicode(false);

データベース列のデータ型の構成

HasColumnType メソッドを使用すると、同じ基本型のさまざまな表現にマッピングできます。 このメソッドを使用しても、実行時にデータの変換を実行することはできません。 IsUnicode は、データベースに依存しないため、列を varchar に設定する場合に推奨される方法であることに注意してください。

modelBuilder.Entity<Department>()   
    .Property(p => p.Name)   
    .HasColumnType("varchar");

複合型のプロパティの構成

複合型でスカラー プロパティを構成するには、2 つの方法があります。

ComplexTypeConfiguration で Property を呼び出すことができます。

modelBuilder.ComplexType<Details>()
    .Property(t => t.Location)
    .HasMaxLength(20);

ドット表記を使用して、複合型のプロパティにアクセスすることもできます。

modelBuilder.Entity<OnsiteCourse>()
    .Property(t => t.Details.Location)
    .HasMaxLength(20);

オプティミスティック コンカレンシー トークンとして使用するプロパティの構成

エンティティ内のプロパティがコンカレンシー トークンを表すように指定するには、ConcurrencyCheck 属性または IsConcurrencyToken メソッドを使用できます。

modelBuilder.Entity<OfficeAssignment>()
    .Property(t => t.Timestamp)
    .IsConcurrencyToken();

IsRowVersion メソッドを使用して、プロパティをデータベースの行バージョンに構成することもできます。 プロパティを行バージョンに設定すると、オプティミスティック コンカレンシー トークンとして自動的に構成されます。

modelBuilder.Entity<OfficeAssignment>()
    .Property(t => t.Timestamp)
    .IsRowVersion();

型マッピング

クラスが複合型であることを指定する

規則により、主キーが指定されていない型は複合型として扱われます。 Code First が複合型を検出しないシナリオがいくつかあります (たとえば、ID というプロパティを持っていても、主キーであるわけではありません)。 このような場合は、fluent API を使用して、型が複合型であることを明示的に指定します。

modelBuilder.ComplexType<Details>();

データベース内のテーブルに CLR エンティティ型をマップしないように指定する

次の例は、CLR 型をデータベース内のテーブルにマップから除外する方法を示しています。

modelBuilder.Ignore<OnlineCourse>();

データベース内の特定のテーブルへのエンティティ型のマッピング

Department のすべてのプロパティは、t_ Department と呼ばれるテーブル内の列にマップされます。

modelBuilder.Entity<Department>()  
    .ToTable("t_Department");

スキーマ名は次のように指定することもできます。

modelBuilder.Entity<Department>()  
    .ToTable("t_Department", "school");

階層ごとのテーブル (TPH) 継承のマッピング

TPH マッピング シナリオでは、継承階層内のすべての型が 1 つのテーブルにマップされます。 識別子列は、各行の型を識別するために使用されます。 Code First を使用してモデルを作成する場合、TPH は継承階層に含まれる型の既定の戦略です。 既定では、識別子列は "識別子" という名前のテーブルに追加され、階層内の各型の CLR 型名が識別子の値に使用されます。 fluent API を使用して、既定の動作を変更できます。

modelBuilder.Entity<Course>()  
    .Map<Course>(m => m.Requires("Type").HasValue("Course"))  
    .Map<OnsiteCourse>(m => m.Requires("Type").HasValue("OnsiteCourse"));

テーブルPer-Type (TPT) 継承のマッピング

TPT マッピング シナリオでは、すべての型が個々のテーブルにマップされます。 基本型または派生型にのみ属するプロパティは、その型にマップされるテーブルに格納されます。 派生型にマップされるテーブルには、派生テーブルとベース テーブルを結合する外部キーも格納されます。

modelBuilder.Entity<Course>().ToTable("Course");  
modelBuilder.Entity<OnsiteCourse>().ToTable("OnsiteCourse");

Table-Per-Concrete クラス (TPC) 継承のマッピング

TPC マッピング シナリオでは、階層内のすべての非抽象型が個々のテーブルにマップされます。 派生クラスにマップされるテーブルには、データベース内の基底クラスにマップされるテーブルとのリレーションシップはありません。 継承されたプロパティを含むクラスのすべてのプロパティは、対応するテーブルの列にマップされます。

MapInheritedProperties メソッドを呼び出して、各派生型を構成します。 MapInheritedProperties は、基底クラスから継承されたすべてのプロパティを、派生クラスのテーブル内の新しい列に再マップします。

TPC 継承階層に参加しているテーブルは主キーを共有しないため、同じ ID シードを持つデータベース生成値がある場合、サブクラスにマップされるテーブルに挿入すると、重複するエンティティ キーが存在することに注意してください。 この問題を解決するには、テーブルごとに異なる初期シード値を指定するか、主キー プロパティで ID をオフにします。 ID は、Code First を使用する場合の整数キー プロパティの既定値です。

modelBuilder.Entity<Course>()
    .Property(c => c.CourseID)
    .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);

modelBuilder.Entity<OnsiteCourse>().Map(m =>
{
    m.MapInheritedProperties();
    m.ToTable("OnsiteCourse");
});

modelBuilder.Entity<OnlineCourse>().Map(m =>
{
    m.MapInheritedProperties();
    m.ToTable("OnlineCourse");
});

エンティティ型のプロパティをデータベース内の複数のテーブルにマッピングする (エンティティ分割)

エンティティ分割を使用すると、エンティティ型のプロパティを複数のテーブルに分散できます。 次の例では、Department エンティティは Department と DepartmentDetails の 2 つのテーブルに分割されています。 エンティティ分割では、Map メソッドの複数の呼び出しを使用して、プロパティのサブセットを特定のテーブルにマップします。

modelBuilder.Entity<Department>()
    .Map(m =>
    {
        m.Properties(t => new { t.DepartmentID, t.Name });
        m.ToTable("Department");
    })
    .Map(m =>
    {
        m.Properties(t => new { t.DepartmentID, t.Administrator, t.StartDate, t.Budget });
        m.ToTable("DepartmentDetails");
    });

データベース内の 1 つのテーブルへの複数のエンティティ型のマッピング (テーブル分割)

次の例では、主キーを共有する 2 つのエンティティ型を 1 つのテーブルにマップします。

modelBuilder.Entity<OfficeAssignment>()
    .HasKey(t => t.InstructorID);

modelBuilder.Entity<Instructor>()
    .HasRequired(t => t.OfficeAssignment)
    .WithRequiredPrincipal(t => t.Instructor);

modelBuilder.Entity<Instructor>().ToTable("Instructor");

modelBuilder.Entity<OfficeAssignment>().ToTable("Instructor");

エンティティ型を Insert/Update/Delete ストアド プロシージャにマッピングする (EF6 以降)

EF6 以降では、エンティティをマップして、挿入の更新と削除にストアド プロシージャを使用できます。 詳細については、「 ストアド プロシージャの最初の挿入/更新/削除をコード化する」を参照してください。

サンプルで使用されるモデル

このページのサンプルでは、次の 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; }
}