Entity-framework-fluent-api

提供:Dev Guides
移動先:案内検索

Entity Framework-Fluent API

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

  • 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メソッドをオーバーライドする必要があります。 次のコードに示すように、学生テーブルの列名を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には、エンティティとそのプロパティを構成して、さまざまなCode First規則をオーバーライドするための重要なメソッドが多数用意されています。 以下はその一部です。

Sr. No. Method Name & Description
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>

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

9

ToTable(String)

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

Fluent APIを使用すると、データベースへのマッピング方法や、それらの相互関係を変更するかどうかにかかわらず、エンティティまたはそのプロパティを構成できます。 構成を使用すると、さまざまなマッピングとモデリングに影響を与えることができます。 Fluent APIがサポートする主なマッピングタイプは次のとおりです-

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

エンティティマッピング

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

  • したがって、これらの構成を追加するためにドメインクラスに入るのではなく、コンテキスト内でこれを行うことができます。
  • 最初のことは、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は、コース、登録、学生などのコンテキストクラスの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メソッドで指定されたテーブル名が表示されます。

OnModelメソッド

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

エンティティ分割を使用すると、複数のテーブルからのデータを単一のクラスに結合できます。また、それらは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プロパティを構成する

次の例では、Course Titleプロパティは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プロパティを構成する

次の例では、IsRequiredメソッドを使用してNotNull列を作成するために、Course Titleプロパティが必要です。 同様に、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");
}

関係の構成

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

  • Code First命名規則を使用する場合、ほとんどの場合、Code Firstを使用して、外部キーとナビゲーションプロパティに基づいてテーブル間の関係を設定できます。
  • それらの規則に合わない場合は、クラス間の関係に影響を与えるために使用できる構成、およびCode Firstで構成を追加するときにデータベースでそれらの関係がどのように実現されるかがあります。
  • それらの一部はデータアノテーションで使用でき、Fluent APIを使用してさらに複雑なものを適用できます。

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は、どのタイプが依存関係であり、どのタイプが関係のプリンシパルであるかを推測できます。 ただし、関係の両端が必要な場合、または両側がオプションの場合、Entity Frameworkは依存関係とプリンシパルを識別できません。 関係の両端が必要な場合、次のコードに示すように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つだけ含まれています。 これは、最も一般的に使用される関係のタイプです。

  • このタイプの関係では、テーブルAの行はテーブルBの多くの一致する行を持つことができますが、テーブルBの行はテーブルAの一致する行を1つだけ持つことができます。
  • 外部キーは、リレーションシップの多端を表すテーブルで定義されます。
  • たとえば、上の図では、StudentテーブルとEnrollmentテーブルには1対1の関係があり、各学生には多くの登録がありますが、各登録は1人の学生にのみ属します。

以下は、1対多の関係を持つStudentとEnrollmentです。ただし、Enrollmentテーブルの外部キーは、デフォルトのCode First規則に従っていません。

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メソッド

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

多対多の関係を構成する

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

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

以下は、StudentとCourseのナビゲーションプロパティであるStudentとCoursesがコレクションであるため、StudentとCourseが多対多の関係にあるStudentとCourseクラスです。 つまり、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 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; }
}

学生とコース間の多対多の関係を構成するには、次のコードに示すようにFluent APIを使用できます。

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

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

結合テーブル

結合テーブル名とテーブル内の列の名前を指定する場合は、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");
   });
}

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

結合テーブル

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