エンティティフレームワーク-FluentAPI

Fluent APIは、データアノテーションでは不可能ないくつかのより高度な構成に加えて、データアノテーションが実行できるすべてをカバーするモデル構成を指定する高度な方法です。データアノテーションとFluentAPIは一緒に使用できますが、CodeFirstはFluentAPI>データアノテーション>デフォルトの規則を優先します。

  • Fluent APIは、ドメインクラスを構成するもう1つの方法です。

  • Code First Fluent APIは、最も一般的には、派生したDbContextのOnModelCreatingメソッドをオーバーライドすることによってアクセスされます。

  • Fluent APIは、DataAnnotationsよりも多くの構成機能を提供します。Fluent APIは、次のタイプのマッピングをサポートしています。

この章では、次のコードに示すように、Student、Course、Enrollmentクラスと、MyContext名を持つ1つのコンテキストクラスを含む簡単な例を続けます。

using System.Data.Entity; 
using System.Linq; 
using System.Text;
using System.Threading.Tasks;  

namespace EFCodeFirstDemo {

   class Program {
      static void Main(string[] args) {}
   }
   
   public enum Grade {
      A, B, C, D, F
   }

   public class Enrollment {
      public int EnrollmentID { get; set; }
      public int CourseID { get; set; }
      public int StudentID { get; set; }
      public Grade? Grade { get; set; }
		
      public virtual Course Course { get; set; }
      public virtual Student Student { get; set; }
   }

   public class Student {
      public int ID { get; set; }
      public string LastName { get; set; }
      public string FirstMidName { get; set; }
		
      public DateTime EnrollmentDate { get; set; }
		
      public virtual ICollection<Enrollment> Enrollments { get; set; }
   }

   public class Course {
      public int CourseID { get; set; }
      public string Title { get; set; }
      public int Credits { get; set; }
		
      public virtual ICollection<Enrollment> Enrollments { get; set; }
   }

   public class MyContext : DbContext {
      public virtual DbSet<Course> Courses { get; set; }
      public virtual DbSet<Enrollment> Enrollments { get; set; }
      public virtual DbSet<Student> Students { get; set; }
   }

}

Fluent APIにアクセスするには、DbContextのOnModelCreatingメソッドをオーバーライドする必要があります。次のコードに示すように、studentテーブルの列名をFirstMidNameからFirstNameに変更する簡単な例を見てみましょう。

public class MyContext : DbContext {

   protected override void OnModelCreating(DbModelBuilder modelBuilder) {
      modelBuilder.Entity<Student>().Property(s ⇒ s.FirstMidName)
      .HasColumnName("FirstName");}

      public virtual DbSet<Course> Courses { get; set; }
      public virtual DbSet<Enrollment> Enrollments { get; set; }
      public virtual DbSet<Student> Students { get; set; }
}

DbModelBuilderは、CLRクラスをデータベーススキーマにマップするために使用されます。これはメインクラスであり、すべてのドメインクラスを構成できます。エンティティデータモデル(EDM)を構築するためのこのコード中心のアプローチは、コードファーストとして知られています。

Fluent APIは、さまざまなコードファースト規則をオーバーライドするようにエンティティとそのプロパティを構成するためのいくつかの重要なメソッドを提供します。以下はそれらのいくつかです。

シニア番号 メソッド名と説明
1

ComplexType<TComplexType>

型を複合型としてモデルに登録し、複合型の構成に使用できるオブジェクトを返します。このメソッドは、同じタイプに対して複数回呼び出して、複数行の構成を実行できます。

2

Entity<TEntityType>

モデルの一部としてエンティティタイプを登録し、エンティティの構成に使用できるオブジェクトを返します。このメソッドは、同じエンティティに対して複数回呼び出して、複数行の構成を実行できます。

3

HasKey<TKey>

このエンティティタイプの主キープロパティを構成します。

4

HasMany<TTargetEntity>

このエンティティタイプから多くの関係を構成します。

5

HasOptional<TTargetEntity>

このエンティティタイプからオプションの関係を構成します。エンティティタイプのインスタンスは、この関係を指定しなくてもデータベースに保存できます。データベース内の外部キーはnull可能になります。

6

HasRequired<TTargetEntity>

このエンティティタイプから必要な関係を構成します。この関係が指定されていない限り、エンティティタイプのインスタンスをデータベースに保存することはできません。データベース内の外部キーはnull不可になります。

7

Ignore<TProperty>

モデルからプロパティを除外して、データベースにマップされないようにします。(StructuralTypeConfiguration <TStructuralType>から継承)

8

Property<T>

このタイプで定義されているstructプロパティを構成します。(StructuralTypeConfiguration <TStructuralType>から継承)

9

ToTable(String)

このエンティティタイプがマップされるテーブル名を構成します。

Fluent APIを使用すると、エンティティまたはそのプロパティを構成できます。データベースへのマッピング方法や相互の関係について変更する必要があります。構成を使用して影響を与えることができるマッピングとモデリングは多種多様です。FluentAPIがサポートするマッピングの主なタイプは次のとおりです-

  • エンティティマッピング
  • プロパティマッピング

エンティティマッピング

エンティティマッピングは、クラスがデータベースにどのようにマッピングされるかについてのEntityFrameworkの理解に影響を与えるいくつかの単純なマッピングです。これらはすべてデータアノテーションで説明しました。ここでは、FluentAPIを使用して同じことを実現する方法を説明します。

  • したがって、これらの構成を追加するためにドメインクラスに入るのではなく、コンテキスト内でこれを行うことができます。

  • まず、OnModelCreatingメソッドをオーバーライドします。これにより、modelBuilderを操作できるようになります。

デフォルトのスキーマ

データベースが生成されるときのデフォルトのスキーマはdboです。DbModelBuilderのHasDefaultSchemaメソッドを使用して、すべてのテーブル、ストアドプロシージャなどに使用するデータベーススキーマを指定できます。

管理スキーマが適用される次の例を見てみましょう。

public class MyContext : DbContext {
   public MyContext() : base("name = MyContextDB") {}

   protected override void OnModelCreating(DbModelBuilder modelBuilder) {
      //Configure default schema
      modelBuilder.HasDefaultSchema("Admin");
   }
	
   public virtual DbSet<Course> Courses { get; set; }
   public virtual DbSet<Enrollment> Enrollments { get; set; }
   public virtual DbSet<Student> Students { get; set; }
}

エンティティをテーブルにマップ

デフォルトの規則では、Code Firstは、Courses、Enrollments、StudentsなどのコンテキストクラスにDbSetプロパティの名前でデータベーステーブルを作成します。ただし、異なるテーブル名が必要な場合は、次のコードに示すように、この規則をオーバーライドして、DbSetプロパティとは異なるテーブル名を指定できます。

protected override void OnModelCreating(DbModelBuilder modelBuilder) {

   //Configure default schema
   modelBuilder.HasDefaultSchema("Admin");

   //Map entity to table
   modelBuilder.Entity<Student>().ToTable("StudentData");
   modelBuilder.Entity<Course>().ToTable("CourseDetail");
   modelBuilder.Entity<Enrollment>().ToTable("EnrollmentInfo");
}

データベースが生成されると、OnModelCreatingメソッドで指定されたテーブル名が表示されます。

エンティティ分割(エンティティを複数のテーブルにマップ)

エンティティ分割を使用すると、複数のテーブルからのデータを1つのクラスに結合でき、テーブル間に1対1の関係があるテーブルでのみ使用できます。学生情報が2つのテーブルにマップされている次の例を見てみましょう。

protected override void OnModelCreating(DbModelBuilder modelBuilder) {
   //Configure default schema
   modelBuilder.HasDefaultSchema("Admin");

   //Map entity to table
   modelBuilder.Entity<Student>().Map(sd ⇒ {
      sd.Properties(p ⇒ new { p.ID, p.FirstMidName, p.LastName });
      sd.ToTable("StudentData");
   })

   .Map(si ⇒ {
      si.Properties(p ⇒ new { p.ID, p.EnrollmentDate });
      si.ToTable("StudentEnrollmentInfo");
   });

   modelBuilder.Entity<Course>().ToTable("CourseDetail");
   modelBuilder.Entity<Enrollment>().ToTable("EnrollmentInfo");
}

上記のコードでは、Mapメソッドを使用して一部のプロパティをStudentDataテーブルにマッピングし、一部のプロパティをStudentEnrollmentInfoテーブルにマッピングすることで、Studentエンティティが次の2つのテーブルに分割されていることがわかります。

  • StudentData −学生のFirstMidNameと姓が含まれます。

  • StudentEnrollmentInfo −EnrollmentDateが含まれています。

データベースが生成されると、次の画像に示すように、データベースに次のテーブルが表示されます。

プロパティマッピング

Propertyメソッドは、エンティティまたは複合型に属する各プロパティの属性を構成するために使用されます。Propertyメソッドは、特定のプロパティの構成オブジェクトを取得するために使用されます。Fluent APIを使用して、ドメインクラスのプロパティをマッピングおよび構成することもできます。

主キーの構成

主キーのデフォルトの規則は次のとおりです。

  • クラスは、名前が「ID」または「Id」であるプロパティを定義します
  • クラス名の後に「ID」または「Id」が続く

次のStudentクラスのコードに示すように、クラスが主キーのデフォルトの規則に従わない場合-

public class Student {
   public int StdntID { get; set; }
   public string LastName { get; set; }
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

次に、プロパティを主キーとして明示的に設定するには、次のコードに示すようにHasKeyメソッドを使用できます。

protected override void OnModelCreating(DbModelBuilder modelBuilder) {
   //Configure default schema
   modelBuilder.HasDefaultSchema("Admin");
	
   // Configure Primary Key
   modelBuilder.Entity<Student>().HasKey<int>(s ⇒ s.StdntID); 
}

列を構成する

Entity Frameworkでは、デフォルトでCode Firstは、同じ名前、順序、およびデータ型を持つプロパティの列を作成します。ただし、次のコードに示すように、この規則をオーバーライドすることもできます。

protected override void OnModelCreating(DbModelBuilder modelBuilder) {

   //Configure default schema
   modelBuilder.HasDefaultSchema("Admin");

   //Configure EnrollmentDate Column
   modelBuilder.Entity<Student>().Property(p ⇒ p.EnrollmentDate)
	
   .HasColumnName("EnDate")
   .HasColumnType("DateTime")
   .HasColumnOrder(2);
}

MaxLengthプロパティを構成する

次の例では、CourseTitleプロパティは24文字以内にする必要があります。ユーザーが24文字より長い値を指定すると、ユーザーはDbEntityValidationException例外を受け取ります。

protected override void OnModelCreating(DbModelBuilder modelBuilder) {
   //Configure default schema
   modelBuilder.HasDefaultSchema("Admin");
   modelBuilder.Entity<Course>().Property(p ⇒ p.Title).HasMaxLength(24);
}

NullまたはNotNullプロパティを構成する

次の例では、Course Titleプロパティが必須であるため、IsRequiredメソッドを使用してNotNull列を作成します。同様に、Student EnrollmentDateはオプションであるため、次のコードに示すように、IsOptionalメソッドを使用してこの列にnull値を許可します。

protected override void OnModelCreating(DbModelBuilder modelBuilder) {

   //Configure default schema
   modelBuilder.HasDefaultSchema("Admin");
   modelBuilder.Entity<Course>().Property(p ⇒ p.Title).IsRequired();
   modelBuilder.Entity<Student>().Property(p ⇒ p.EnrollmentDate).IsOptional();
	
   //modelBuilder.Entity<Student>().Property(s ⇒ s.FirstMidName)
   //.HasColumnName("FirstName"); 
}

関係の構成

データベースのコンテキストでのリレーションシップは、一方のテーブルにもう一方のテーブルの主キーを参照する外部キーがある場合に、2つのリレーショナルデータベーステーブルの間に存在する状況です。Code Firstを使用する場合は、ドメインCLRクラスを定義してモデルを定義します。既定では、Entity Frameworkはコードファーストの規則を使用して、クラスをデータベーススキーマにマップします。

  • コードファーストの命名規則を使用する場合、ほとんどの場合、コードファーストを使用して、外部キーとナビゲーションプロパティに基づいてテーブル間の関係を設定できます。

  • それらがこれらの規則に適合しない場合は、クラス間の関係に影響を与えるために使用できる構成と、CodeFirstで構成を追加するときにデータベースでそれらの関係がどのように実現されるかについてもあります。

  • それらのいくつかはデータ注釈で利用可能であり、FluentAPIを使用してさらに複雑なものを適用できます。

1対1の関係を構成する

モデルで1対1の関係を定義するときは、各クラスで参照ナビゲーションプロパティを使用します。データベースでは、両方のテーブルが関係のいずれかの側に1つのレコードしか持つことができません。各主キー値は、関連するテーブルの1つのレコードのみに関連します(またはレコードなし)。

  • 関連する両方の列が主キーであるか、一意の制約がある場合、1対1の関係が作成されます。

  • 1対1の関係では、主キーは追加で外部キーとして機能し、どちらのテーブルにも個別の外部キー列はありません。

  • このように関連するほとんどの情報はすべて1つのテーブルにあるため、このタイプの関係は一般的ではありません。

次の例を見てみましょう。モデルに別のクラスを追加して、1対1の関係を作成します。

public class Student {
   public int ID { get; set; }
   public string LastName { get; set; }
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual StudentLogIn StudentLogIn { get; set; }
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

public class StudentLogIn {
   [Key, ForeignKey("Student")]
   public int ID { get; set; }
   public string EmailID { get; set; }
   public string Password { get; set; }
	
   public virtual Student Student { get; set; }
}

上記のコードでわかるように、Key属性とForeignKey属性は、StudentLogInクラスのIDプロパティに使用され、主キーと外部キーとしてマークされています。

Fluent APIを使用してStudentとStudentLogInの間に1対0または1の関係を構成するには、次のコードに示すようにOnModelCreatingメソッドをオーバーライドする必要があります。

protected override void OnModelCreating(DbModelBuilder modelBuilder) {

   //Configure default schema
   modelBuilder.HasDefaultSchema("Admin");

   // Configure ID as PK for StudentLogIn
   modelBuilder.Entity<StudentLogIn>()
   .HasKey(s ⇒ s.ID);

   // Configure ID as FK for StudentLogIn
   modelBuilder.Entity<Student>()
   
   .HasOptional(s ⇒ s.StudentLogIn) //StudentLogIn is optional
   .WithRequired(t ⇒ t.Student); // Create inverse relationship
}

ほとんどの場合、Entity Frameworkは、どのタイプが依存関係であり、どのタイプが関係のプリンシパルであるかを推測できます。ただし、関係の両端が必要な場合、または両側がオプションの場合、EntityFrameworkは依存関係とプリンシパルを識別できません。関係の両端が必要な場合は、次のコードに示すようにHasRequiredを使用できます。

protected override void OnModelCreating(DbModelBuilder modelBuilder) {

   //Configure default schema
   modelBuilder.HasDefaultSchema("Admin");

   // Configure ID as PK for StudentLogIn
   modelBuilder.Entity<StudentLogIn>()
   .HasKey(s ⇒ s.ID);

   // Configure ID as FK for StudentLogIn
   modelBuilder.Entity<Student>()
   .HasRequired(r ⇒ r.Student)
   .WithOptional(s ⇒ s.StudentLogIn);  
}

データベースが生成されると、次の図に示すように関係が作成されていることがわかります。

1対多の関係を構成する

主キーテーブルには、関連テーブルのレコードなし、1つ、または多数に関連するレコードが1つだけ含まれています。これは、最も一般的に使用されるタイプの関係です。

  • このタイプの関係では、テーブルAの行はテーブルBの多くの一致する行を持つことができますが、テーブルBの行はテーブルAの1つの一致する行のみを持つことができます。

  • 外部キーは、関係の多端を表すテーブルで定義されます。

  • たとえば、上の図では、StudentテーブルとEnrollmentテーブルには1つの関係があり、各学生には多くの登録がありますが、各登録は1人の学生にのみ属します。

以下は、1対多の関係にあるStudentとEnrollmentですが、Enrollmentテーブルの外部キーはデフォルトのCodeFirst規則に従っていません。

public class Enrollment {
   public int EnrollmentID { get; set; }
   public int CourseID { get; set; }
	
   //StdntID is not following code first conventions name
   public int StdntID { get; set; }
   public Grade? Grade { get; set; }
	
   public virtual Course Course { get; set; }
   public virtual Student Student { get; set; }
}

public class Student {
   public int ID { get; set; }
   public string LastName { get; set; }
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual StudentLogIn StudentLogIn { get; set; }
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

この場合、Fluent APIを使用して1対多の関係を構成するには、次のコードに示すようにHasForeignKeyメソッドを使用する必要があります。

protected override void OnModelCreating(DbModelBuilder modelBuilder) {

   //Configure default schema
   modelBuilder.HasDefaultSchema("Admin");

   //Configure FK for one-to-many relationship
   modelBuilder.Entity<Enrollment>()

   .HasRequired<Student>(s ⇒ s.Student)
   .WithMany(t ⇒ t.Enrollments)
   .HasForeignKey(u ⇒ u.StdntID);  
}

データベースが生成されると、次の図に示すように関係が作成されていることがわかります。

上記の例では、HasRequiredメソッドは、StudentナビゲーションプロパティがNullでなければならないことを指定しています。したがって、登録を追加または更新するたびに、StudentにEnrollmentエンティティを割り当てる必要があります。これを処理するには、HasRequiredメソッドの代わりにHasOptionalメソッドを使用する必要があります。

多対多の関係を構成する

両方のテーブルの各レコードは、他のテーブルの任意の数のレコード(またはレコードなし)に関連付けることができます。

  • このような関係を作成するには、ジャンクションテーブルと呼ばれる3番目のテーブルを定義します。このテーブルの主キーは、テーブルAとテーブルBの両方の外部キーで構成されます。

  • たとえば、StudentテーブルとCourseテーブルには多対多の関係があります。

以下は、StudentとCourseがコレクションであるナビゲーションプロパティStudentsとCoursesを持っているため、StudentとCourseが多くの関係を持っているStudentクラスとCourseクラスです。つまり、あるエンティティには別のエンティティコレクションがあります。

public class Student {
   public int ID { get; set; }
   public string LastName { get; set; }
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual ICollection<Course> Courses { get; set; }
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

public class Course {
   public int CourseID { get; set; }
   public string Title { get; set; }
   public int Credits { get; set; }
	
   public virtual ICollection<Student> Students { get; set; }
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

StudentとCourseの間に多対多の関係を構成するには、次のコードに示すようにFluentAPIを使用できます。

protected override void OnModelCreating(DbModelBuilder modelBuilder) {

   //Configure default schema
   modelBuilder.HasDefaultSchema("Admin");

   // Configure many-to-many relationship
   modelBuilder.Entity<Student>()
   .HasMany(s ⇒ s.Courses) 
   .WithMany(s ⇒ s.Students);
}

デフォルトのCodeFirst規則は、データベースの生成時に結合テーブルを作成するために使用されます。その結果、次の図に示すように、StudentCoursesテーブルがCourse_CourseID列とStudent_ID列で作成されます。

結合テーブル名とテーブル内の列の名前を指定する場合は、Mapメソッドを使用して追加の構成を行う必要があります。

protected override void OnModelCreating(DbModelBuilder modelBuilder) {

   //Configure default schema
   modelBuilder.HasDefaultSchema("Admin");

   // Configure many-to-many relationship 
   modelBuilder.Entity<Student>()

   .HasMany(s ⇒ s.Courses)
   .WithMany(s ⇒ s.Students)
   
   .Map(m ⇒ {
      m.ToTable("StudentCoursesTable");
      m.MapLeftKey("StudentID");
      m.MapRightKey("CourseID");
   }); 
}

データベースが生成されると、上記のコードで指定されているようにテーブルと列の名前が作成されていることがわかります。

理解を深めるために、上記の例を段階的に実行することをお勧めします。