トレーニングコース。 ASP.NET MVCアプリケーションの複雑なデータモデルの作成、パート2

これは、Entity FrameworkおよびASP.NET MVC 3開発記事シリーズの拡張機能であり、最初の章は次のリンクにあります。

前のレッスンでは、3つのエンティティで構成される単純なデータモデルを操作する方法を学びました。 このレッスンでは、いくつかのエンティティとそれらの間の関係を追加し、注釈を使用してモデルクラスを管理する方法を学習します。



コースの本質に関する変更



image42



モデル \ コースで csは、以前に生成されたコードを次のものに置き換えます。



using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace ContosoUniversity.Models { public class Course { [DatabaseGenerated(DatabaseGeneratedOption.None)] [Display(Name = "Number")] public int CourseID { get; set; } [Required(ErrorMessage = "Title is required.")] [MaxLength(50)] public string Title { get; set; } [Required(ErrorMessage = "Number of credits is required.")] [Range(0,5,ErrorMessage="Number of credits must be between 0 and 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; } } }
      
      





DatabaseGenerated属性


CourseIDプロパティにNoneパラメータが指定されたDatabaseGenerated属性は、主キー値がユーザーによって設定され、データベースによって生成されないことを決定します。



 [DatabaseGenerated(DatabaseGeneratedOption.None)] [Display(Name = "Number")] public int CourseID { get; set; }
      
      





既定では、Entity Frameworkはデータベースによる主キーの自動生成を想定していますが、これはほとんどの状況で必要です。 ただし、コースエンティティは、ある学部では1000、別の学部では2000など、ユーザー定義の数値を使用します。



外部キーとナビゲーションプロパティ


Courseエンティティの外部キープロパティとナビゲーションプロパティは、次の関係を反映しています。

コースは1つの学部に関連付けられているため、外部キーのDepartmentIDとDepartmentナビゲーションプロパティがあります。



public int DepartmentID {get; セット; }

パブリックバーチャル部門Department {get; セット; }



無制限の数の学生がコースに参加できるため、登録ナビゲーションプロパティがあります。



パブリック仮想ICollection登録{get; セット; }



コースはさまざまな教師が教えることができるため、Instructorsナビゲーションプロパティがあります。



public virtual ICollection <インストラクター>インストラクター{get; セット; }



部署エンティティの作成



image47



モデル \ Departmentを作成します 次のコードを含むcs



 using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace ContosoUniversity.Models { public class Department { public int DepartmentID { get; set; } [Required(ErrorMessage = "Department name is required.")] [MaxLength(50)] public string Name { get; set; } [DisplayFormat(DataFormatString="{0:c}")] [Required(ErrorMessage = "Budget is required.")] [Column(TypeName="money")] public decimal? Budget { get; set; } [DisplayFormat(DataFormatString="{0:d}", ApplyFormatInEditMode=true)] [Required(ErrorMessage = "Start date is required.")] public DateTime StartDate { get; set; } [Display(Name="Administrator")] public int? InstructorID { get; set; } public virtual Instructor Administrator { get; set; } public virtual ICollection<Course> Courses { get; set; } } }
      
      





列属性


以前は、Column属性を使用して列名のマッピングを変更しました。 Departmentエンティティのコードでは、この属性を使用してSQLデータ型のマッピングを変更します。つまり、列はSQL Serverデータ型でデータベースに定義されます。



 [Column(TypeName="money")] public decimal? Budget { get; set; }
      
      





エンティティフレームワークは、プロパティに定義されているCLR型に基づいて最適なデータ型を自動的に選択するため、これは通常必要ありません。 CLRの10進数型がSQL Serverの10進数型になると仮定します。 ただし、この場合、プロパティには通貨に関連する数字が含まれることが確実であり、タイププロパティmoneyはこのプロパティに最適です。



外部キーとナビゲーションプロパティ


外部キーとナビゲーションプロパティは、次の関係を反映しています。

教員は管理者を含む場合と含まない場合があり、管理者は常に=教師です。 したがって、InstructorIDプロパティはInstructorエンティティの外部キーとして定義され、int型の後の疑問符は、プロパティがnull値を許可する可能性があることを示します。 ナビゲーションプロパティAdministratorには、エンティティインストラクターが含まれています。



public int? InstructorID {get; セット; }



パブリック仮想インストラクター管理者{get; セット; }

教員は多くのコースを持つことができるため、コースナビゲーションプロパティが存在します。



パブリック仮想ICollectionコース{get; セット; }



規則では、Entity Frameworkがnull不可の外部キーを削除し、多対多の関係で削除することを指定しています。 これにより、反復的なカスケード削除が発生し、コードの実行時に例外が発生する可能性があります。 たとえば、Department.InstructorIDをNULL可能として定義しない場合、「参照関係により、許可されない循環参照が発生する」場合に、次の例外を受け取ります。



学生エンティティの変更



Models \ Studentで csはコードを次のように置き換えます。



 using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace ContosoUniversity.Models { public class Student { public int StudentID { get; set; } [Required(ErrorMessage = "Last name is required.")] [Display(Name="Last Name")] [MaxLength(50)] public string LastName { get; set; } [Required(ErrorMessage = "First name is required.")] [Column("FirstName")] [Display(Name = "First Name")] [MaxLength(50)] public string FirstMidName { get; set; } [Required(ErrorMessage = "Enrollment date is required.")] [DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)] [Display(Name = "Enrollment Date")] public DateTime? EnrollmentDate { get; set; } public string FullName { get { return LastName + ", " + FirstMidName; } } public virtual ICollection<Enrollment> Enrollments { get; set; } } }
      
      





登録エンティティの変更



image52



モデル \ 登録で csはコードを次のように置き換えます。



 using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace ContosoUniversity.Models { public class Enrollment { public int EnrollmentID { get; set; } public int CourseID { get; set; } public int StudentID { get; set; } [DisplayFormat(DataFormatString="{0:#.#}",ApplyFormatInEditMode=true,NullDisplayText="No grade")] public decimal? Grade { get; set; } public virtual Course Course { get; set; } public virtual Student Student { get; set; } } }
      
      





外部キーとナビゲーションプロパティ


外部キーとナビゲーションプロパティは、次の関係を反映しています。

各コースエントリエンティティは1つのコースに対応するため、外部キーCourseIDとCourseナビゲーションプロパティがあります。



public int CourseID {get; セット; }



パブリックバーチャルコースコース{get; セット; }

各学生エントリエンティティは1人の学生に対応するため、StudentID外部キーとStudentナビゲーションプロパティがあります。



public int StudentID {get; セット; }



public virtual Student Student {get; セット; }



多対多の関係


学生とコースのエンティティは多対多の関係に関連しており、登録エンティティはデータベース内のペイロード持つ多対多の結合テーブルに対応しています。 つまり、登録テーブルには、結合テーブルの外部キー(この場合は主キーとGradeプロパティ)に加えて追加データが含まれます。



以下の画像は、Entity Frameworkデザイナーによって生成されたエンティティダイアグラムの形式で関係を示しています。



image55



各リンク行の一方の端には1があり、もう一方の端には*があり、1対多の関係を示します。



登録テーブルに成績情報が含まれていない場合、必要な外部キーはCourseIDとStudentIDの2つだけです。 その場合、データベースにペイロードのない多対多の結合テーブル(または純粋な結合テーブル )に対応し、モデルクラスをまったく作成する必要はありません。 エンティティインストラクターとコースは、同様の多対多の関係によって接続されています。ご覧のとおり、エンティティクラスはありません。



image58



結合テーブルが必要ですが:



image61



Entity Frameworkは、Instructor.CoursesおよびCourse.Instructorsナビゲーションプロパティを介して間接的にアクセスされるCourseInstructorテーブルを自動的に作成します。



DisplayFormat属性


GradeプロパティのDisplayFormat属性は、要素の書式を決定します。



 [DisplayFormat(DataFormatString="{0:#.#}",ApplyFormatInEditMode=true,NullDisplayText="No grade")] public decimal? Grade { get; set; }
      
      







エンティティ図の関係



下の図は、学校モデルの関係システムを示しています。



image64



多対多の関係(*-*)および1対多の関係(1- *)に加えて、InstructorとOfficeAssignmentエンティティの間の1対0または1つの関係(1-0..1)も確認できます。およびnull対1または多対(0..1-*)部門およびインストラクター。



データベースコンテキストの構成



次に、SchoolContextクラスに新しいエンティティを追加し、マッピングを構成します。 場合によっては、一部の機能では属性が存在しないため、属性の代わりにメソッドを使用する必要があります。 その他の場合、メソッドまたは属性を使用することを選択できます(一部の人々は属性を使用しないことを好みます)。



DAL \ SchoolContextのコードを置き換えます csへ:



 using System; using System.Collections.Generic; using System.Data.Entity; using ContosoUniversity.Models; using System.Data.Entity.ModelConfiguration.Conventions; namespace ContosoUniversity.Models { public class SchoolContext : DbContext { 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.Conventions.Remove<PluralizingTableNameConvention>(); modelBuilder.Entity<Instructor>() .HasOptional(p => p.OfficeAssignment).WithRequired(p => p.Instructor); modelBuilder.Entity<Course>() .HasMany(c => c.Instructors).WithMany(i => i.Courses) .Map(t => t.MapLeftKey("CourseID") .MapRightKey("InstructorID") .ToTable("CourseInstructor")); modelBuilder.Entity<Department>() .HasOptional(x => x.Administrator); } } }
      
      





OnModelCreatingメソッドは、次の関係を定義します。

インストラクターとOfficeAssignmentの間で1対0または1:



modelBuilder.Entity <インストラクター>().HasOptional(p => p.OfficeAssignment).WithRequired(p => p.Instructor);



インストラクターとコースの間の多対多。 コードは、結合されたテーブルのテーブルと列を定義します。 Code Firstは、コードなしで多対多の関係を構成できますが、呼び出さない場合、InstructorIDのInstructorInstructorIDなど、列の標準名が使用されます。



modelBuilder.Entity <コース>()

.HasMany(c => c。インストラクター).WithMany(i => i.Courses)

.Map(t => t.MapLeftKey( "CourseID")

.MapRightKey( "InstructorID")

.ToTable( "CourseInstructor"));



Department.Administratorナビゲーションプロパティを使用して、DepartmentとInstructorの間で1対0または1つ:



modelBuilder.Entity <Department>().HasOptional(x => x.Administrator);



「舞台裏」で行われていることの詳細については、ASP.NETユーザー教育チームのブログでFluent APIを読むことができます。



データベースにテストデータを入力する



その前に、 DAL \ SchoolInitializerを作成しました データベースにテストデータを入力するcs 。 ここで、古いコードを新しいものに置き換えます。これにより、新しいエンティティの存在が考慮されます。



 using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Data.Entity; using ContosoUniversity.Models; namespace ContosoUniversity.DAL { public class SchoolInitializer : DropCreateDatabaseIfModelChanges<SchoolContext> { protected override void Seed(SchoolContext context) { var students = new List<Student> { new Student { FirstMidName = "Carson", LastName = "Alexander", EnrollmentDate = DateTime.Parse("2005-09-01") }, new Student { FirstMidName = "Meredith", LastName = "Alonso", EnrollmentDate = DateTime.Parse("2002-09-01") }, new Student { FirstMidName = "Arturo", LastName = "Anand", EnrollmentDate = DateTime.Parse("2003-09-01") }, new Student { FirstMidName = "Gytis", LastName = "Barzdukas", EnrollmentDate = DateTime.Parse("2002-09-01") }, new Student { FirstMidName = "Yan", LastName = "Li", EnrollmentDate = DateTime.Parse("2002-09-01") }, new Student { FirstMidName = "Peggy", LastName = "Justice", EnrollmentDate = DateTime.Parse("2001-09-01") }, new Student { FirstMidName = "Laura", LastName = "Norman", EnrollmentDate = DateTime.Parse("2003-09-01") }, new Student { FirstMidName = "Nino", LastName = "Olivetto", EnrollmentDate = DateTime.Parse("2005-09-01") } }; students.ForEach(s => context.Students.Add(s)); context.SaveChanges(); var instructors = new List<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") } }; instructors.ForEach(s => context.Instructors.Add(s)); context.SaveChanges(); var departments = new List<Department> { new Department { Name = "English", Budget = 350000, StartDate = DateTime.Parse("2007-09-01"), InstructorID = 1 }, new Department { Name = "Mathematics", Budget = 100000, StartDate = DateTime.Parse("2007-09-01"), InstructorID = 2 }, new Department { Name = "Engineering", Budget = 350000, StartDate = DateTime.Parse("2007-09-01"), InstructorID = 3 }, new Department { Name = "Economics", Budget = 100000, StartDate = DateTime.Parse("2007-09-01"), InstructorID = 4 } }; departments.ForEach(s => context.Departments.Add(s)); context.SaveChanges(); var courses = new List<Course> { new Course { CourseID = 1050, Title = "Chemistry", Credits = 3, DepartmentID = 3, Instructors = new List<Instructor>() }, new Course { CourseID = 4022, Title = "Microeconomics", Credits = 3, DepartmentID = 4, Instructors = new List<Instructor>() }, new Course { CourseID = 4041, Title = "Macroeconomics", Credits = 3, DepartmentID = 4, Instructors = new List<Instructor>() }, new Course { CourseID = 1045, Title = "Calculus", Credits = 4, DepartmentID = 2, Instructors = new List<Instructor>() }, new Course { CourseID = 3141, Title = "Trigonometry", Credits = 4, DepartmentID = 2, Instructors = new List<Instructor>() }, new Course { CourseID = 2021, Title = "Composition", Credits = 3, DepartmentID = 1, Instructors = new List<Instructor>() }, new Course { CourseID = 2042, Title = "Literature", Credits = 4, DepartmentID = 1, Instructors = new List<Instructor>() } }; courses.ForEach(s => context.Courses.Add(s)); context.SaveChanges(); courses[0].Instructors.Add(instructors[0]); courses[0].Instructors.Add(instructors[1]); courses[1].Instructors.Add(instructors[2]); courses[2].Instructors.Add(instructors[2]); courses[3].Instructors.Add(instructors[3]); courses[4].Instructors.Add(instructors[3]); courses[5].Instructors.Add(instructors[3]); courses[6].Instructors.Add(instructors[3]); context.SaveChanges(); var enrollments = new List<Enrollment> { new Enrollment { StudentID = 1, CourseID = 1050, Grade = 1 }, new Enrollment { StudentID = 1, CourseID = 4022, Grade = 3 }, new Enrollment { StudentID = 1, CourseID = 4041, Grade = 1 }, new Enrollment { StudentID = 2, CourseID = 1045, Grade = 2 }, new Enrollment { StudentID = 2, CourseID = 3141, Grade = 4 }, new Enrollment { StudentID = 2, CourseID = 2021, Grade = 4 }, new Enrollment { StudentID = 3, CourseID = 1050 }, new Enrollment { StudentID = 4, CourseID = 1050, }, new Enrollment { StudentID = 4, CourseID = 4022, Grade = 4 }, new Enrollment { StudentID = 5, CourseID = 4041, Grade = 3 }, new Enrollment { StudentID = 6, CourseID = 1045 }, new Enrollment { StudentID = 7, CourseID = 3141, Grade = 2 }, }; enrollments.ForEach(s => context.Enrollments.Add(s)); context.SaveChanges(); var officeAssignments = new List<OfficeAssignment> { new OfficeAssignment { InstructorID = 1, Location = "Smith 17" }, new OfficeAssignment { InstructorID = 2, Location = "Gowan 27" }, new OfficeAssignment { InstructorID = 3, Location = "Thompson 304" }, }; officeAssignments.ForEach(s => context.OfficeAssignments.Add(s)); context.SaveChanges(); } } }
      
      





インストラクターエンティティとの多対多の関係に関連付けられているコースエンティティの処理に注意してください。



 var courses = new List { new Course { CourseID = 1050, Title = "Chemistry", Credits = 3, DepartmentID = 3, Instructors = new List() }, ... }; courses.ForEach(s => context.Courses.Add(s)); context.SaveChanges(); courses[0].Instructors.Add(instructors[0]); ... context.SaveChanges();
      
      





Courseオブジェクトを空のコレクションとして作成する場合(Instructors = new List())。これにより、Instructor.Add()メソッドを使用してCourseに関連付けられたInstructorエンティティを追加できます。 空のリストを作成しなかった場合、Instructorsプロパティはnullになり、Addメソッドがないため、このような関係を追加することはできません。



プロジェクトを実動サーバーにデプロイする前に、すべてのデータベース初期化コードを削除することを忘れないでください。



データベースを削除して再作成する



プロジェクトを実行し、学生インデックスページを選択します。



image67



このページは以前と同じように見えますが、「舞台裏」でデータベースが削除され、再作成されました。



ページが開かない場合、またはSchool.sdfファイルが既に使用されているというエラーが表示された場合(下の画像)、サーバーエクスプローラーを再度開き、データベース接続を閉じてから、もう一度ページを開いてください。



image70



その後、サーバーエクスプローラーでデータベースを開き、テーブルの新しいテーブルを確認します。



image75



EdmMetadataに加えて、CourseInstructorクラスを作成しなかったテーブルに注意してください。 これは、インストラクターとコースを組み合わせた表です。



CourseInstructorテーブルをクリックし、[ テーブルデータの表示 ]をクリックして、以前に追加したデータがCourse.Instructorsナビゲーションプロパティであることを確認します。



image80



これで、洗練されたデータモデルと適切なデータベースができました。 さらに、データにアクセスするさまざまな方法を学びます。



謝辞



Alexander Belotserkovsky( ahriman )の翻訳にご協力いただきありがとうございます。



All Articles