Entity Framework Code First でのリレーションシップ
こんにちは。ザクです。遅めの夏休みをとってリフレッシュしてきました。こう涼しいと夏休み感はゼロですね。
今日はCode Firstでモデル定義する際のリレーションシップの設定方法について書きます。こちらRailsほどちゃんとした情報がなくて大変困ったやつです。Microsoftさんにおかれましては、善処していただきたく存じます。
One-to-Manyリレーションシップ
簡単なのから行きます。1:Nの関係の場合です。
Creating an Entity Framework Data Model for an ASP.NET MVC Application (1 of 10) | The ASP.NET Site
前回紹介したチュートリアルで作成したモデルが早速そうなっています。StudentとEnrollmentの関係がそうですね。モデルのダイアグラム(Visual Studio 2012から自動生成できます。方法はまた後日説明します)を見ると、Student側に「1」、Enrollment側に「*」がついています。つまりStudentが親で、Enrollmentが子という関係です。では、コードをみてみましょう。
public class Student { public int StudentId { get; set; } public string LastName { get; set; } public string FirstMidName { get; set; } public DateTime EnrollmentDate { get; set; } public virtual ICollection<Enrollment> Enrollments { get; set; } }
CodeFirstではモデルを作成する際、最初のintの名前にモデル名が入っていたら、規約に従いそのintを主キーにします。規約から外れた名前をキーに付けたい場合は、[Key]属性をそのintの上に付けることで主キーにできます。
最後の行のvirtualが関係を定義している部分です。ダイアグラムではこの部分はモデルの「ナビゲーションプロパティ」と呼ばれる下の部分に入ります。(ナビゲーションプロパティについては追って説明します)ICollectionなので、コレクションになっており、次の<>の中にモデル名、最後にこのモデル上での名前をつけます。規約にしたがい、モデル上の名前は、モデルを複数形にします。これで、Student(親)側の定義ができました。
次に子供のEnrollment側のコードです。
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; } }
こちらには鍵になるintが複数定義されています。最初のintだけは自分自身のidでそれ以外は、外部キー(FK)になります。親を持つモデルには、必ず親となるモデルのキーが定義されていなければなりません。
virtualの部分も異なります。こちらはコレクションになっていません。親は一つなので当然です。この場合virtualの後には、モデル名、このモデル上の名前を書きます。規約に従いモデル上の名前には、親のモデル名を単数形で書いてください。
以上で、One-to-Manyの関係が作れます。
Many-to-Manyリレーションシップ
次にわかりやすいのは、N:Nです。
N:Nの定義の方法は2種類あります。2つのテーブルをリンクするテーブルを自分で定義して作るか、Code Firstに作らせるかです。前者の方は、N:Nのリンクテーブルの中にもプロパティを持たせたいときに使います。後者の方は、純粋なN:N用のリンクテーブルを作るときに使います。
まず前者のケースから。これはチュートリアルのEnrollmentモデルが参考になります。親の鍵を2つ持ち、virtualのナビゲーションプロパティも2つ持ちます。(上のEnrollmentを参考)このテーブルはGradeという、このモデルで使うプロパティを持つので、このように作っています。
次は純粋なN:Nリンクテーブルです。こちらはモデルを定義せずに、DALファイルの中にテーブル間の関係を定義するコードを書きます。
先にこの関係に使うモデルを定義しましょう。Instructorモデルを作ります。(namespaceに注意してください)
using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace MvcApplication1.Models { public class Instructor { public int InstructorId { get; set; } [Required] [Display(Name = "Last Name")] [StringLength(50)] public string LastName { get; set; } [Required] [Column("FirstName")] [Display(Name = "First Name")] [StringLength(50)] public string FirstMidName { get; set; } [DataType(DataType.Date)] [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)] [Display(Name = "Hire Date")] public DateTime HireDate { get; set; } public string FullName { get { return LastName + ", " + FirstMidName; } } public virtual ICollection<Course> Courses { get; set; } public virtual OfficeAssignment OfficeAssignment { get; set; } } }
次にCourseモデルを作ります。
using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace MvcApplication1.Models { public class Course { [DatabaseGenerated(DatabaseGeneratedOption.None)] [Display(Name = "Number")] public int CourseId { get; set; } [StringLength(50, MinimumLength = 3)] public string Title { get; set; } [Range(0, 5)] public int Credits { get; set; } [Display(Name = "Department")] public int DepartmentId { get; set; } public virtual Department Department { get; set; } public virtual ICollection<Enrollment> Enrollments { get; set; } public virtual ICollection<Instructor> Instructors { get; set; } }
最後にDALを修正します。
public class SchoolContext : DbContext { : base("DefaultConnection") { } public DbSet<Course> Courses { get; set; } public DbSet<Department> Departments { get; set; } public DbSet<Enrollment> Enrollments { get; set; } public DbSet<Instructor> Instructors { get; set; } public DbSet<Student> Students { get; set; } public DbSet<OfficeAssignment> OfficeAssignments { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<Course>() .HasMany(c => c.Instructors).WithMany(i => i.Courses) .Map(t => t.MapLeftKey("CourseId") .MapRightKey("InstructorId") .ToTable("CourseInstructor")); } }
DALに記述するコードはこんな風になります。DALを作った時にしたようにDbSetにCourseとInstructorモデルの定義を追加したら、その下のOnModelCreatingの中で、Entityの定義をしてやります。ここでは、Courseから見てInstructorモデルを.HasManyでつなぎ、Instructorから見たCourseを.WithManyでつないでいます。.Map以下でそれぞれが持つFKと出来上がったテーブルの名前を定義します。
N:Nの定義はこんな感じです。
One-to-Zero or Oneリレーションシップ
最後に1:1です。1:1の定義方法も2種類ですが、普通のOne-to-Oneはまたこんど説明します。説明が多くなるので、よく使う簡単なZero or Oneの方を先に説明します。
チュートリアルでは、InstructorとOfficeAssignmentモデルがこれにあたります。先に作ったInstructorの中にOfficeAssignmentのナビゲーションプロパティがあったと思います。ここでは、OfficeAssignmentを作りましょう。
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace MvcApplication1.Models { public class OfficeAssignment { [Key] [ForeignKey("Instructor")] public int InstructorId { get; set; } [StringLength(50)] [Display(Name = "Office Location")] public string Location { get; set; } public virtual Instructor Instructor { get; set; } } }
1:1の子テーブルの特徴は、自身のモデル名がついたIdを持たないことです。代わりに親のIdを主キーとして持ちます。InstructorIdに2つの属性[Key]と[ForeignKey("Instructor")]がついていますね。つまり、このモデルはInstructorIdを主キーであり、親のInstructorモデルへのFKでもあるということです。ナビゲーションプロパティは、1:Nの時と同様に子モデルなので、1つの親を単数形で定義します。1:1の簡単な方の定義は以上です。
ここまでできたらチュートリアルを参考にシードデータも更新しましょう。(チュートリアルでは、Departmentという1:Nのモデルも定義しているので、こちらを作っておくのをお忘れなく。コピペしたデータにこのモデル用のデータも入っているのでエラーが出ますよ)