.NET Core MVCとEF Coreのチュートリアルを分かりやすくまとめました③(複合データモデル)

スポンサーリンク
プログラミング

.NET Core MVCとEF Coreのチュートリアルについても分かりやすく解説するブログの第3回です。前回の記事はこちらです。

当記事では以下のASP.NET Core MVCとEntity Framework Coreの公式チュートリアルをより分かりやすくまとめたものです。

チュートリアルで分かりにくい表現を言い換えて解説しています。成果物はチュートリアルと同様です。第3回では、複合データモデルの作成を見ていきます。(公式チュートリアルのPart5)

このチュートリアルでやること

前回は3つのエンティティクラスで構成されたデータモデルで進めてきましたが、ここからは更に4つのエンティティクラスを追加し、より複雑なケースを扱います。

エンティティクラスの完成イメージは以下の通りです。

Entity diagram

でははじめましょう!

Studentクラスを更新する

Student entity

ここでは作成済みのクラスと追加するクラスを実装し、上記の画像のようにデータモデルを完成させます。

StudentクラスにDataType属性を追加

アプリを実行してみると、生徒の登録日には日付と時刻が表示されています。しかし、実際には時刻は不要で日付データだけが表示されていてほしいです。このような場合、モデルにDataType属性を付与することでデータの表示をするすべてのビューの表示形式をまとめて変更できます。

Models/Student.csを以下のように変更します。

using System; 
using System.Collections.Generic; 
using System.ComponentModel.DataAnnotations;  //左記を追記

namespace ContosoUniversity.Models 
{
   public class Student
   {
       public int ID { get; set; }
       public string LastName { get; set; } 
       public string FirstMidName { get; set; }  
       //以下を追記
       [DataType(DataType.Date)] 
       [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]  
       public DateTime EnrollmentDate { get; set; } 
       //ここまで
       public ICollection<Enrollment> Enrollments { get; set; } } } = new List<Enrollment>();
   }
}

DataType 属性は、データベースの組み込み型よりも具体的なデータ型を指定する際に使用します。

DataType 列挙型は、Date、Time、PhoneNumber、Currency、EmailAddress などがあります。DataType 属性を付与すると、アプリケーションで型固有の機能を自動的に提供することもできます。
例:DataType.EmailAddressを付与するとmailto:リンクが作成される。
例:DataType.Date を付与すると日付セレクターが表示される。
DataType 属性は、HTML5ブラウザーが認識できる HTML5 data-属性を出力します。

DisplayFormat 属性は、日付の書式を明示的に指定しています。

 

アプリを実行し、[Students]をクリックすると登録時刻が表示されなくなったことが確認できます。

StudentクラスにStringLength属性を追加

データの入力規則を検証し、エラーメッセージを表示する属性もあります。 例えば、StringLength 属性では、データベースのデータの最大長を指定できます。ユーザーが50文字を超える名前を入力しないようにする必要があるとします。 この制限を追加するには、次の例のように、StringLength 属性を LastName および FirstMidName プロパティに追加します。

using System; 
using System.Collections.Generic; 
using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models 
{
   public class Student
   {
       public int ID { get; set; }
       [StringLength(50)] //左記を追記
       public string LastName { get; set; } 
       [StringLength(50)] //左記を追記
       public string FirstMidName { get; set; }  
       [DataType(DataType.Date)] 
       [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]  
       public DateTime EnrollmentDate { get; set; } 
       public ICollection<Enrollment> Enrollments { get; set; } } } = new List<Enrollment>();
   }
}

他にも正規表現を利用して入力規則に制限をかけることができます。例えば以下は最初の1文字列を英大文字、残りを英字にするよう指定したものです。

[RegularExpression(@"^[A-Z]+[a-zA-Z]*$")]

StringLengthはデータベースにも変更が入るため、更新を反映させるにはマイグレーションが必要です。以下のコマンドを入力しておきましょう。

dotnet ef migrations add MaxLengthOnNames
dotnet ef database update

データベースが更新出来たら、アプリを実行し、 [Students] タブを選択して、 [新規作成] をクリックし、50 文字を超えるいずれかの名前を入力してみます。 入力欄が50字までになっていることが確認出来ればOKです。(ここではアルファベットを順に入力してみました。a~zを繰り返し入力すると、2週目のxで入力出来なくなっています。)

StudentクラスにColumn属性を追加

FirstMidNameというプロパティがありますが、データベースのカラム名はFirstNameにする必要があるようなケースではColumn属性が使えます。

using System; 
using System.Collections.Generic; 
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; //左記を追記

namespace ContosoUniversity.Models 
{
   public class Student
   {
       public int ID { get; set; }
       [StringLength(50)] 
       public string LastName { get; set; } 
       [StringLength(50)] 
       [Column("FirstName")] //左記を追記
       public string FirstMidName { get; set; }  
       [DataType(DataType.Date)] 
       [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]  
       public DateTime EnrollmentDate { get; set; } 
       public ICollection<Enrollment> Enrollments { get; set; } } } = new List<Enrollment>();
   }
}

Column属性はデータベースに変更が入るため、更新を反映させるにはマイグレーションが必要です。以下のコマンドを入力しておきましょう。

dotnet ef migrations add ColumnFirstName
dotnet ef database update

これらの変更はSQL Serverオブジェクトエクスプローラーでdbo.Studentを開いて確認できます。

StudentクラスにRequired属性とDisplay属性を追加

必須入力にさせたいプロパティにはRequired属性を付与します。表示名を指定する場合はDisplay属性を使用します。

using System; 
using System.Collections.Generic; 
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; 

namespace ContosoUniversity.Models 
{
   public class Student
   {
       public int ID { get; set; }
       [Required] //左記を追記
       [StringLength(50)] 
       [Display(Name = "Last Name")] //左記を追記
       public string LastName { get; set; } 
       [Required] //左記を追記
       [StringLength(50)] 
       [Column("FirstName")]
       [Display(Name = "First Name")] //左記を追記
       public string FirstMidName { get; set; }  
       [DataType(DataType.Date)] 
       [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
       [Display(Name = "Enrollment Date")] //左記を追記
       public DateTime EnrollmentDate { get; set; } 
       //以下を追記
       [Display(Name = "Full Name")]
       public string FullName
       {
          get
          {
             return LastName + ", " + FirstMidName;
          }
       }
       //ここまで
       public ICollection<Enrollment> Enrollments { get; set; } } } = new List<Enrollment>();
   }
}

FullNameプロパティは2つのプロパティを連結した値を返しますが、データベースにFullNameカラムは生成されません。

Instructorクラスを作成する

Instructor entity

Models/Instructor.cs を作成し、以下に置き換えます。

using System; 
using System.Collections.Generic; 
using System.ComponentModel.DataAnnotations; 
using System.ComponentModel.DataAnnotations.Schema; 

namespace ContosoUniversity.Models 
{
  public class Instructor
  {
    public int ID { 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; }
    [Display(Name = "Full Name")]
    public string FullName 
    { 
       get { return LastName + ", " + FirstMidName; }
    }
    public ICollection<CourseAssignment>? CourseAssignments { get; set; }
    public OfficeAssignment? OfficeAssignment { get; set; } 
  }
}

ナビゲーションプロパティにCourseAssignmentsOfficeAssignmentがあります。

この大学では、講師は複数のコースを担当する可能性があるため、ICollectionのCourseAssignmentsを持ちます。一方で、オフィスは1つのみ持つことができるため、OfficeAssignmentプロパティでは単一の OfficeAssignmentエンティティを持ちます。(オフィスを持たない教員もいます。)

OfficeAssignmentクラスを作成する

OfficeAssignment entity

Models/OfficeAssignment.cs を作成します。

using System.ComponentModel.DataAnnotations; 
using System.ComponentModel.DataAnnotations.Schema; 

namespace ContosoUniversity.Models 
{
  public class OfficeAssignment 
  {
    [Key]
    public int InstructorID { get; set; }
    [StringLength(50)]
    [Display(Name = "Office Location")]
    public string? Location { get; set; }
    public Instructor? Instructor { get; set; } 
  }
}

Key属性は主キーを明示的に定義する属性です。EF CoreではIDやclassnameIDといった命名規則に従う場合に主キーとして認識されますが、OfficeAssignmentのInstructorIDはどちらの規則にも当てはまりません。(ここでは講師が複数のオフィスを持つことはないため、InstructorIDを主キーにしても問題ありません。)

Courseクラスを更新する

Course entity

Models/Course.cs を以下のコードに更新します。

using System.Collections.Generic; 
using System.ComponentModel.DataAnnotations; //左記を追記
using System.ComponentModel.DataAnnotations.Schema; 

namespace ContosoUniversity.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; }
    public int DepartmentID { get; set; }//左記を追記
    public Department? Department { get; set; }//左記を追記
    public ICollection<Enrollment>? Enrollments { get; set; }
    public ICollection<CourseAssignment>? CourseAssignments { get; set; }//左記を追記
  } 
}

Entity Framework では、関連エンティティのナビゲーションプロパティがある場合、自動で外部キーを作成してくれます。もし、DepartmentIDプロパティを実装しなくてもEFがDepartmentプロパティから外部キーが必要であることを自動的に判断し、DepartmentIDが作成されます。これをシャドウプロパティと言います。

しかし、シャドウプロパティを使用するよりも外部キーとなるプロパティが定義されている方がデータの更新を簡単に行えます。

DatabaseGenerated属性について

Entity Frameworkでは主キー値がデータベース側で生成されることを前提に動作しています。しかし、場合によっては主キーの値を1,2,3と連番以外にしたいケースがあると思います。(例えば、経済学科は1000、経営学科は2000というようにユーザー指定のコース番号を指定したい場合)

DatabaseGenerated属性のOptionをNoneに設定することで主キーの自動生成をオフにできます。

Departmentクラスを作成する

Department entity

Models/Department.cs を作成します。

using System; 
using System.Collections.Generic; 
using System.ComponentModel.DataAnnotations; 
using System.ComponentModel.DataAnnotations.Schema; 

namespace ContosoUniversity.Models 
{
  public class Department
  {
    public int DepartmentID { get; set; }
    [StringLength(50, MinimumLength = 3)]
    public string Name { get; set; }
    [DataType(DataType.Currency)]
    [Column(TypeName = "money")]
    public decimal Budget { get; set; }
    [DataType(DataType.Date)]
    [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
    [Display(Name = "Start Date")]
    public DateTime StartDate { get; set; }
    public int? InstructorID { get; set; }
    public Instructor Administrator { get; set; }
    public ICollection<Course> Courses { get; set; } 
  } 
}

Column属性について

Column属性はSQLデータ型のマッピングを変更するために使用されます。今回、BudgetプロパティはC#のdecimal型で定義されています。規定ではEFによってSQLServerのdecimal型にマッピングされます。

しかし、Budgetは予算、つまりお金を表すデータです。SQLServerにはmoney型が存在するため、ここではそちらに変換されるよう明示的に指定するためにColumn属性を用いています。

Enrollmentクラスを更新する

Enrollment entity

Models/Enrollment.cs を以下の通りに更新します。

using System.ComponentModel.DataAnnotations; //左記を追記
using System.ComponentModel.DataAnnotations.Schema;  //左記を追記

namespace ContosoUniversity.Models 
{
   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; }
      [DisplayFormat(NullDisplayText = "No grade")]  //左記を追記
      public Grade? Grade { get; set; }

      public Course Course { get; set; }
      public Student Student { get; set; }
   } 
}

DisplayFormat属性について

Gradeは成績を保持するデータです。成績はコースの終了後に入力されるため、データがない場合(null)が想定されるとこれまでのチュートリアルで話しました。

アプリの表示としてはデータがnullの場合にnullと表示されるよりもNo grade(成績なし)と表示された方が適切です。ここではNullの場合に表示させるテキストを設定しています。

多対多のリレーションシップについて

コースエンティティと生徒エンティティは多対多のリレーションシップがあります。登録エンティティは2つのテーブルの関係を繋ぐテーブルとして存在しています。

CourseAssignmentクラスを作成する

Models/CourseAssignment.cs を作成します。

using System; 
using System.Collections.Generic; 
using System.ComponentModel.DataAnnotations; 
using System.ComponentModel.DataAnnotations.Schema; 

namespace ContosoUniversity.Models 
{
   public class CourseAssignment
   {
       public int InstructorID { get; set; }
       public int CourseID { get; set; }
       public Instructor? Instructor { get; set; }
       public Course? Course { get; set; }
   } 
}

CourseAssignmentはCourseエンティティとInstructorエンティティの多対多のリレーションシップのみをもつエンティティです。一般的にはこのようなテーブルは両方のエンティティの名前を繋げてCourseInstructorとすることが多いですが、Microsoftのチュートリアルではビジネス要件に合わせてテーブル名を設定することを推奨しています。

また、このエンティティはInstructorIDとCourseIDの複合主キーを持ちます。複合主キーはデータベースコンテキストで設定する必要があります。

複合主キーとは
複数のカラムの組み合わせで一意な主キーとして扱うことです。今回の例だと、1つのコースを担当する講師は1人です。そのビジネスルールを使用して、コースIDと講師IDの組み合わせを複合主キーにしています。複合主キーは設計で一意性を保証するため、運用後や仕様変更で一意性が失われることもあリます。

データベースコンテキストを更新する

Data/SchoolContext.cs を以下の通りに更新します。

using ContosoUniversity.Models; 
using Microsoft.EntityFrameworkCore; 

namespace ContosoUniversity.Data
{
   public class SchoolContext : DbContext 
   {
      public SchoolContext(DbContextOptions<SchoolContext> options) : base(options) { }

      public DbSet<Course> Courses { get; set; }
      public DbSet<Enrollment> Enrollments { get; set; }
      public DbSet<Student> Students { get; set; }
      //以下を追記
      public DbSet<Department> Departments { get; set; }
      public DbSet<Instructor> Instructors { get; set; }
      public DbSet<OfficeAssignment> OfficeAssignments { get; set; }
      public DbSet<CourseAssignment> CourseAssignments { get; set; }
      //ここまで

      protected override void OnModelCreating(ModelBuilder modelBuilder) 
      {
          modelBuilder.Entity<Course>().ToTable("Course"); 
          modelBuilder.Entity<Enrollment>().ToTable("Enrollment"); 
          modelBuilder.Entity<Student>().ToTable("Student"); 
          //以下を追記
          modelBuilder.Entity<Department>().ToTable("Department");
          modelBuilder.Entity<Instructor>().ToTable("Instructor");
          modelBuilder.Entity<OfficeAssignment>().ToTable("OfficeAssignment"); 
          modelBuilder.Entity<CourseAssignment>().ToTable("CourseAssignment"); 
          modelBuilder.Entity<CourseAssignment>()
              .HasKey(c => new { c.CourseID, c.InstructorID }); 
          //ここまで
      }
   }
 }

複合主キーはHasKeyで設定されていることに注意してください。

データベースの初期データを設定する

Data/DbInitializer.cs のコードを以下に置き換えてください。

using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using ContosoUniversity.Models;

namespace ContosoUniversity.Data
{
   public static class DbInitializer
   {
      public static void Initialize(SchoolContext context)
      {
         //context.Database.EnsureCreated(); 

         // Look for any students.
         if (context.Students.Any())
         {
            return; // DB has been seeded
         }
         var students = new Student[]
         {
            new Student { FirstMidName = "Carson", LastName = "Alexander",
                EnrollmentDate = DateTime.Parse("2010-09-01") },
            new Student { FirstMidName = "Meredith", LastName = "Alonso",
                EnrollmentDate = DateTime.Parse("2012-09-01") },
            new Student { FirstMidName = "Arturo", LastName = "Anand",
                EnrollmentDate = DateTime.Parse("2013-09-01") },
            new Student { FirstMidName = "Gytis", LastName = "Barzdukas",
                EnrollmentDate = DateTime.Parse("2012-09-01") },
            new Student { FirstMidName = "Yan", LastName = "Li",
                EnrollmentDate = DateTime.Parse("2012-09-01") },
            new Student { FirstMidName = "Peggy", LastName = "Justice",
                EnrollmentDate = DateTime.Parse("2011-09-01") },
            new Student { FirstMidName = "Laura", LastName = "Norman",
                EnrollmentDate = DateTime.Parse("2013-09-01") },
            new Student { FirstMidName = "Nino", LastName = "Olivetto",
                EnrollmentDate = DateTime.Parse("2005-09-01") }
         };
         foreach (Student s in students)
         {
            context.Students.Add(s);
         }
         context.SaveChanges();
         var instructors = new Instructor[]
         {
            new Instructor { FirstMidName = "Kim", LastName = "Abercrombie",
                HireDate = DateTime.Parse("1995-03-11") },
            new Instructor { FirstMidName = "Fadi", LastName = "Fakhouri",
                HireDate = DateTime.Parse("2002-07-06") },
            new Instructor { FirstMidName = "Roger", LastName = "Harui",
                HireDate = DateTime.Parse("1998-07-01") },
            new Instructor { FirstMidName = "Candace", LastName = "Kapoor",
                HireDate = DateTime.Parse("2001-01-15") },
            new Instructor { FirstMidName = "Roger", LastName = "Zheng",
                HireDate = DateTime.Parse("2004-02-12") }
         };
         foreach (Instructor i in instructors)
         {
            context.Instructors.Add(i);
         }
         context.SaveChanges();
         var departments = new Department[]
         {
            new Department { Name = "English", Budget = 350000,
                StartDate = DateTime.Parse("2007-09-01"),
                InstructorID = instructors.Single( i => i.LastName == "Abercrombie").ID },
            new Department { Name = "Mathematics", Budget = 100000,
                StartDate = DateTime.Parse("2007-09-01"),
                InstructorID = instructors.Single( i => i.LastName == "Fakhouri").ID },
            new Department { Name = "Engineering", Budget = 350000,
                StartDate = DateTime.Parse("2007-09-01"),
                InstructorID = instructors.Single( i => i.LastName == "Harui").ID },
            new Department { Name = "Economics", Budget = 100000,
                StartDate = DateTime.Parse("2007-09-01"),
                InstructorID = instructors.Single( i => i.LastName == "Kapoor").ID }
         };
         foreach (Department d in departments)
         {
            context.Departments.Add(d);
         }
         context.SaveChanges();
         var courses = new Course[]
         {
            new Course {CourseID = 1050, Title = "Chemistry", Credits = 3,
                DepartmentID = departments.Single( s => s.Name == "Engineering").DepartmentID
            },
            new Course {CourseID = 4022, Title = "Microeconomics", Credits = 3,
                DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID
            },
            new Course {CourseID = 4041, Title = "Macroeconomics", Credits = 3,
                DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID
            },
            new Course {CourseID = 1045, Title = "Calculus", Credits = 4,
                DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID
            },
            new Course {CourseID = 3141, Title = "Trigonometry", Credits = 4,
                DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID
            },
            new Course {CourseID = 2021, Title = "Composition", Credits = 3,
                DepartmentID = departments.Single( s => s.Name == "English").DepartmentID
            },
            new Course {CourseID = 2042, Title = "Literature", Credits = 4,
                DepartmentID = departments.Single( s => s.Name == "English").DepartmentID
            },
         };
         foreach (Course c in courses)
         {
            context.Courses.Add(c);
         }
         context.SaveChanges();
         var officeAssignments = new OfficeAssignment[]
         {
            new OfficeAssignment {
                InstructorID = instructors.Single( i => i.LastName == "Fakhouri").ID,
                Location = "Smith 17" },
            new OfficeAssignment {
                InstructorID = instructors.Single( i => i.LastName == "Harui").ID,
                Location = "Gowan 27" },
            new OfficeAssignment {
                InstructorID = instructors.Single( i => i.LastName == "Kapoor").ID,
                Location = "Thompson 304" },
         };
         foreach (OfficeAssignment o in officeAssignments)
         {
            context.OfficeAssignments.Add(o);
         }
         context.SaveChanges();
         var courseInstructors = new CourseAssignment[]
         {
            new CourseAssignment {
                CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
                InstructorID = instructors.Single(i => i.LastName == "Kapoor").ID
            },
            new CourseAssignment {
                CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
                InstructorID = instructors.Single(i => i.LastName == "Harui").ID
            },
            new CourseAssignment {
                CourseID = courses.Single(c => c.Title == "Microeconomics" ).CourseID,
                InstructorID = instructors.Single(i => i.LastName == "Zheng").ID
            },
            new CourseAssignment {
                CourseID = courses.Single(c => c.Title == "Macroeconomics" ).CourseID,
                InstructorID = instructors.Single(i => i.LastName == "Zheng").ID
            },
            new CourseAssignment {
                CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID,
                InstructorID = instructors.Single(i => i.LastName == "Fakhouri").ID
            },
            new CourseAssignment {
                CourseID = courses.Single(c => c.Title == "Trigonometry" ).CourseID,
                InstructorID = instructors.Single(i => i.LastName == "Harui").ID
            },
            new CourseAssignment {
                CourseID = courses.Single(c => c.Title == "Composition" ).CourseID,
                InstructorID = instructors.Single(i => i.LastName == "Abercrombie").ID
            },
            new CourseAssignment {
                CourseID = courses.Single(c => c.Title == "Literature" ).CourseID,
                InstructorID = instructors.Single(i => i.LastName == "Abercrombie").ID
            },
         };
         foreach (CourseAssignment ci in courseInstructors)
         {
            context.CourseAssignments.Add(ci);
         }
         context.SaveChanges();
         var enrollments = new Enrollment[]
         {
            new Enrollment {
                StudentID = students.Single(s => s.LastName == "Alexander").ID,
                CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
                Grade = Grade.A
            },
            new Enrollment {
                StudentID = students.Single(s => s.LastName == "Alexander").ID,
                CourseID = courses.Single(c => c.Title == "Microeconomics" ).CourseID,
                Grade = Grade.C
            },
            new Enrollment {
                StudentID = students.Single(s => s.LastName == "Alexander").ID,
                CourseID = courses.Single(c => c.Title == "Macroeconomics" ).CourseID,
                Grade = Grade.B
            },
            new Enrollment {
                StudentID = students.Single(s => s.LastName == "Alonso").ID,
                CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID,
                Grade = Grade.B
            },
            new Enrollment {
                StudentID = students.Single(s => s.LastName == "Alonso").ID,
                CourseID = courses.Single(c => c.Title == "Trigonometry" ).CourseID,
                Grade = Grade.B
            },
            new Enrollment {
                StudentID = students.Single(s => s.LastName == "Alonso").ID,
                CourseID = courses.Single(c => c.Title == "Composition" ).CourseID,
                Grade = Grade.B
            },
            new Enrollment {
                StudentID = students.Single(s => s.LastName == "Anand").ID,
                CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID
            },
            new Enrollment {
                StudentID = students.Single(s => s.LastName == "Anand").ID,
                CourseID = courses.Single(c => c.Title == "Microeconomics").CourseID,
                Grade = Grade.B
            },
            new Enrollment {
                StudentID = students.Single(s => s.LastName == "Barzdukas").ID,
                CourseID = courses.Single(c => c.Title == "Chemistry").CourseID,
                Grade = Grade.B
            },
            new Enrollment {
                StudentID = students.Single(s => s.LastName == "Li").ID,
                CourseID = courses.Single(c => c.Title == "Composition").CourseID,
                Grade = Grade.B
            },
            new Enrollment {
                StudentID = students.Single(s => s.LastName == "Justice").ID,
                CourseID = courses.Single(c => c.Title == "Literature").CourseID,
                Grade = Grade.B
            }
         };
         foreach (Enrollment e in enrollments)
         {
            var enrollmentInDataBase = context.Enrollments.Where(
                s =>
                     s.Student.ID == e.StudentID &&
                     s.Course.CourseID == e.CourseID).SingleOrDefault();
            if (enrollmentInDataBase == null)
            {
               context.Enrollments.Add(e);
            }
         }
         context.SaveChanges();
      }
   }
}

ではデータモデルの更新をデータベースにも反映させていきましょう。

マイグレーションする

ターミナルを開き、マイグレーションファイルを作成するコマンドを実行します。

dotnet ef migrations add ComplexDataModel

テーブルの更新によるデータ消失

現時点でデータベースを更新すると以下のようなエラーメッセージが表示されます。

The ALTER TABLE statement conflicted with the FOREIGN KEY constraint “FK_dbo.Course_dbo.Department_DepartmentID”. The conflict occurred in database “ContosoUniversity”, table “dbo.Department”, column ‘DepartmentID’.

現時点ではデータベースに以前の初期化データが存在しています。これはCourseテーブルにDepartmentIDが追加され、外部キー制約が付与されているためです。初期化データには外部キーに当たるデータがないため、外部キーを含むDepartmentIDカラムの追加マイグレーションに失敗してしまいます。これを回避する方法は初期化データを削除するか、仮のDepartmentIDを挿入することです。マイグレーションの動作を変更するために<timestamp>_ComplexDataModelUpメソッドを修正していきましょう。

<timestamp>_ComplexDataModel.cs ファイルを開きます。DepartmentID列をCourseテーブルに追加するコードの行をコメントアウトします。

migrationBuilder.AlterColumn<string>(
   name: "Title",
   table: "Course",
   maxLength: 50,
   nullable: true,
   oldClrType: typeof(string),
   oldNullable: true);
//以下をコメントアウト
//migrationBuilder.AddColumn<int>(
//   name: "DepartmentID",
//   table: "Course",
//   nullable: false,
//   defaultValue: 0);
//ここまで

Department テーブルを作成するコードの後に、次の強調表示されたコードを追加します。

migrationBuilder.CreateTable(
   name: "Department",
   columns: table => new
   {
      DepartmentID = table.Column<int>(nullable: false)
            .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
      Budget = table.Column<decimal>(type: "money", nullable: false),
      InstructorID = table.Column<int>(nullable: true),
      Name = table.Column<string>(maxLength: 50, nullable: true),
      StartDate = table.Column<DateTime>(nullable: false)
   },
   constraints: table =>
   {
      table.PrimaryKey("PK_Department", x => x.DepartmentID);
      table.ForeignKey(
         name: "FK_Department_Instructor_InstructorID",
         column: x => x.InstructorID,
         principalTable: "Instructor",
         principalColumn: "ID",
         onDelete: ReferentialAction.Restrict);
   });
//以下を追記
migrationBuilder.Sql("INSERT INTO dbo.Department (Name, Budget, StartDate) VALUES ('Temp', 0.00, GETDATE())");
// Default value for FK points to department created above, with
// defaultValue changed to 1 in following AddColumn statement.

migrationBuilder.AddColumn<int>(
   name: "DepartmentID",
   table: "Course",
   nullable: false,
   defaultValue: 1);
//ここまで

これで、新しいエンティティの初期化データを空のデータベースに追加する準備ができました。

接続文字列を変更するかDBを削除する

EFで新しい空のデータベースを作成するには、appsettings.jsonの接続文字列のデータベース名をContosoUniversity3に変更するか、データベースを削除したのちに再作成します。

"ConnectionStrings": {
   "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=ContosoUniversity3;Trusted_Connection=True;MultipleActiveResultSets=true"
},

もしくはターミナルで以下のコマンドを実行し、データベースを削除します。(どちらかを行なってください。)

dotnet ef database drop

データベースを更新する

続いてデータベースを更新します。

dotnet ef database update

アプリを実行すると、DbInitializer.Initialize メソッドが実行され、新しいデータベースが設定されます。

前の手順で行ったようにSQLSever オブジェクトエクスプローラーでデータベースを開き、Tablesノードを展開して、テーブルがすべて作成されたことを確認します(前に開いたものがそのままの状態の場合は、[更新]ボタンをクリックします)。

アプリを実行して、データベースをシードする初期化子コードをトリガーします。CourseAssignment テーブルを右クリックして、 [データの表示] を選択し、テーブルにデータがあることを確認します。

このチュートリアルはここまでです!次は以下のチュートリアルに進みましょう!

コメント

タイトルとURLをコピーしました