Contoso University Web 應用演示了如何使用 EF Core 和 Visual Studio 創(chuàng)建 Razor 頁面 Web 應用。若要了解系列教程,請參閱第一個教程。
前面的教程介紹了由三個實體組成的基本數(shù)據(jù)模型。 本教程將演示如何:
已完成數(shù)據(jù)模型的實體類如下圖所示:
如果遇到無法解決的問題,請下載已完成應用。
此部分將使用特性自定義數(shù)據(jù)模型。
學生頁面當前顯示注冊日期。 通常情況下,日期字段僅顯示日期,不顯示時間。
用以下突出顯示的代碼更新 Models/Student.cs:
C#
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; }
}
}
DataType 特性指定比數(shù)據(jù)庫內(nèi)部類型更具體的數(shù)據(jù)類型。 在此情況下,應僅顯示日期,而不是日期加時間。 DataType 枚舉提供多種數(shù)據(jù)類型,例如日期、時間、電話號碼、貨幣、電子郵件地址等。應用還可通過 DataType 特性自動提供類型特定的功能。 例如:
DataType 特性發(fā)出 HTML 5 data-(讀作 data dash)特性供 HTML 5 瀏覽器使用。 DataType 特性不提供驗證。
DataType.Date 不指定顯示日期的格式。 默認情況下,日期字段根據(jù)基于服務器的 CultureInfo 的默認格式進行顯示。
DisplayFormat 特性用于顯式指定日期格式:
C#
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
ApplyFormatInEditMode 設置指定還應對編輯 UI 應用該格式設置。 某些字段不應使用 ApplyFormatInEditMode。 例如,編輯文本框中通常不應顯示貨幣符號。
DisplayFormat 特性可由其本身使用。 搭配使用 DataType 特性和 DisplayFormat 特性通常是很好的做法。 DataType 特性按照數(shù)據(jù)在屏幕上的呈現(xiàn)方式傳達數(shù)據(jù)的語義。 DataType 特性可提供 DisplayFormat 中所不具有的以下優(yōu)點:
有關詳細信息,請參閱 <input> 標記幫助器文檔。
運行應用。 導航到學生索引頁。 將不再顯示時間。 使用 Student 模型的每個視圖將顯示日期,不顯示時間。
可使用特性指定數(shù)據(jù)驗證規(guī)則和驗證錯誤消息。 StringLength 特性指定數(shù)據(jù)字段中允許的字符的最小長度和最大長度。 StringLength 特性還提供客戶端和服務器端驗證。 最小值對數(shù)據(jù)庫架構(gòu)沒有任何影響。
使用以下代碼更新 Student 模型:
C#
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, ErrorMessage = "First name cannot be longer than 50 characters.")]
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; }
}
}
上面的代碼將名稱限制為不超過 50 個字符。 StringLength 特性不會阻止用戶在名稱中輸入空格。RegularExpression 特性用于向輸入應用限制。 例如,以下代碼要求第一個字符為大寫,其余字符按字母順序排列:
C#
[RegularExpression(@"^[A-Z]+[a-zA-Z""'\s-]*$")]
運行應用:
在“SQL Server 對象資源管理器”(SSOX) 中,雙擊 Student 表,打開 Student 表設計器。
上圖顯示 Student 表的架構(gòu)。 名稱字段的類型為 nvarchar(MAX),因為數(shù)據(jù)庫上尚未運行遷移。 稍后在本教程中運行遷移時,名稱字段將變成 nvarchar(50)。
特性可以控制類和屬性映射到數(shù)據(jù)庫的方式。 在本部分,Column 特性用于將 FirstMidName 屬性的名稱映射到數(shù)據(jù)庫中的“FirstName”。
創(chuàng)建數(shù)據(jù)庫后,模型上的屬性名將用作列名(使用 Column 特性時除外)。
Student 模型使用 FirstMidName 作為名字字段,因為該字段也可能包含中間名。
用以下突出顯示的代碼更新 Student.cs 文件:
C#
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, ErrorMessage = "First name cannot be longer than 50 characters.")]
[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; }
}
}
進行上述更改后,應用中的 Student.FirstMidName 將映射到 Student 表的 FirstName 列。
添加 Column 特性后,SchoolContext 的支持模型會發(fā)生改變。 SchoolContext 的支持模型將不再與數(shù)據(jù)庫匹配。 如果在執(zhí)行遷移前運行應用,則會生成如下異常:
SQL
SqlException: Invalid column name 'FirstName'.
若要更新數(shù)據(jù)庫:
Add-Migration ColumnFirstName
Update-Database
migrations add ColumnFirstName 命令將生成如下警告消息:
text
An operation was scaffolded that may result in the loss of data.
Please review the migration for accuracy.
生成警告的原因是名稱字段現(xiàn)已限制為 50 個字符。 如果數(shù)據(jù)庫中的名稱超過 50 個字符,則第 51 個字符及后面的所有字符都將丟失。
在 SSOX 中打開 Student 表:
執(zhí)行遷移前,名稱列的類型為 nvarchar (MAX)。 名稱列現(xiàn)在的類型為 nvarchar(50)。 列名已從 FirstMidName 更改為 FirstName。
備注
在下一部分中,在某些階段生成應用會生成編譯器錯誤。 說明用于指定生成應用的時間。
用以下代碼更新 Models/Student.cs:
C#
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, ErrorMessage = "First name cannot be longer than 50 characters.")]
[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; }
}
}
Required 特性使名稱屬性成為必填字段。 值類型(DateTime、int、double)等不可為 NULL 的類型不需要 Required 特性。 系統(tǒng)會將不可為 NULL 的類型自動視為必填字段。
不能用 StringLength 特性中的最短長度參數(shù)替換 Required 特性:
C#
[Display(Name = "Last Name")]
[StringLength(50, MinimumLength=1)]
public string LastName { get; set; }
Display 特性指定文本框的標題欄應為“FirstName”、“LastName”、“FullName”和“EnrollmentDate”。標題欄默認不使用空格分隔詞語,如“Lastname”。
FullName 是計算屬性,可返回通過串聯(lián)兩個其他屬性創(chuàng)建的值。 FullName 不能設置并且僅具有一個 get 訪問器。 數(shù)據(jù)庫中不會創(chuàng)建任何 FullName 列。
用以下代碼創(chuàng)建 Models/Instructor.cs:
C#
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; }
}
}
一行可包含多個特性。 可按如下方式編寫 HireDate 特性:
C#
[DataType(DataType.Date),Display(Name = "Hire Date"),DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
CourseAssignments 和 OfficeAssignment 是導航屬性。
一名講師可以教授任意數(shù)量的課程,因此 CourseAssignments 定義為集合。
C#
public ICollection<CourseAssignment> CourseAssignments { get; set; }
如果導航屬性包含多個實體:
導航屬性類型包括:
如果指定了 ICollection<T>,EF Core 會默認創(chuàng)建 HashSet<T> 集合。
CourseAssignment 實體在“多對多關系”部分進行介紹。
Contoso University 業(yè)務規(guī)則規(guī)定一名講師最多可獲得一間辦公室。 OfficeAssignment 屬性包含一個 OfficeAssignment 實體。 如果未分配辦公室,則 OfficeAssignment 為 NULL。
C#
public OfficeAssignment OfficeAssignment { get; set; }
用以下代碼創(chuàng)建 Models/OfficeAssignment.cs:
C#
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] 特性用于在屬性名不是 classnameID 或 ID 時將屬性標識為主鍵 (PK)。
Instructor 和 OfficeAssignment 實體之間存在一對零或一關系。 僅當與分配到辦公室的講師之間建立關系時才存在辦公室分配。 OfficeAssignment PK 也是其到 Instructor 實體的外鍵 (FK)。 EF Core 無法自動將 InstructorID 識別為 OfficeAssignment 的 PK,因為:
因此,Key 特性用于將 InstructorID 識別為 PK:
C#
[Key]
public int InstructorID { get; set; }
默認情況下,EF Core 將鍵視為非數(shù)據(jù)庫生成,因為該列面向的是識別關系。
Instructor 實體的 OfficeAssignment 導航屬性可以為 NULL,因為:
OfficeAssignment 實體具有不可為 NULL 的 Instructor 導航屬性,因為:
當 Instructor 實體具有相關 OfficeAssignment 實體時,每個實體都具有對其導航屬性中的另一個實體的引用。
[Required] 特性可以應用于 Instructor 導航屬性:
C#
[Required]
public Instructor Instructor { get; set; }
上面的代碼指定必須存在相關的講師。 上面的代碼沒有必要,因為 InstructorID 外鍵(也是 PK)不可為 NULL。
用以下代碼更新 Models/Course.cs:
C#
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; }
}
}
Course 實體具有外鍵 (FK) 屬性 DepartmentID。 DepartmentID 指向相關的 Department 實體。 Course 實體具有 Department 導航屬性。
當數(shù)據(jù)模型具有相關實體的導航屬性時,EF Core 不要求此模型具有 FK 屬性。
EF Core 可在數(shù)據(jù)庫中的任何所需位置自動創(chuàng)建 FK。 EF Core 為自動創(chuàng)建的 FK 創(chuàng)建陰影屬性。 數(shù)據(jù)模型中包含 FK 后可使更新更簡單和更高效。 例如,假設某個模型中不包含 FK 屬性 DepartmentID。當提取 Course 實體進行編輯時:
如果數(shù)據(jù)模型中包含 FK 屬性 DepartmentID,則無需在更新前提取 Department 實體。
[DatabaseGenerated(DatabaseGeneratedOption.None)] 特性指定 PK 由應用程序提供而不是由數(shù)據(jù)庫生成。
C#
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }
默認情況下,EF Core 假定 PK 值由數(shù)據(jù)庫生成。 由數(shù)據(jù)庫生成 PK 值通常是最佳方法。 Course 實體的 PK 由用戶指定。 例如,對于課程編號,數(shù)學系可以使用 1000 系列的編號,英語系可以使用 2000 系列的編號。
DatabaseGenerated 特性還可用于生成默認值。 例如,數(shù)據(jù)庫可以自動生成日期字段以記錄數(shù)據(jù)行的創(chuàng)建或更新日期。 有關詳細信息,請參閱生成的屬性。
Course 實體中的外鍵 (FK) 屬性和導航屬性可反映以下關系:
課程將分配到一個系,因此將存在 DepartmentID FK 和 Department 導航屬性。
C#
public int DepartmentID { get; set; }
public Department Department { get; set; }
參與一門課程的學生數(shù)量不定,因此 Enrollments 導航屬性是一個集合:
C#
public ICollection<Enrollment> Enrollments { get; set; }
一門課程可能由多位講師講授,因此 CourseAssignments 導航屬性是一個集合:
C#
public ICollection<CourseAssignment> CourseAssignments { get; set; }
CourseAssignment 在后文介紹。
用以下代碼創(chuàng)建 Models/Department.cs:
C#
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 特性以前用于更改列名映射。 在 Department 實體的代碼中,Column 特性用于更改 SQL 數(shù)據(jù)類型映射。 Budget 列通過數(shù)據(jù)庫中的 SQL Server 貨幣類型進行定義:
C#
[Column(TypeName="money")]
public decimal Budget { get; set; }
通常不需要列映射。 EF Core 通常基于屬性的 CLR 類型選擇相應的 SQL Server 數(shù)據(jù)類型。 CLR decimal 類型會映射到 SQL Server decimal 類型。 Budget 用于貨幣,但貨幣數(shù)據(jù)類型更適合貨幣。
FK 和導航屬性可反映以下關系:
導航屬性名為 Administrator,但其中包含 Instructor 實體:
C#
public int? InstructorID { get; set; }
public Instructor Administrator { get; set; }
上面代碼中的問號 (?) 指定屬性可以為 NULL。
一個系可以有多門課程,因此存在 Course 導航屬性:
C#
public ICollection<Course> Courses { get; set; }
注意:按照約定,EF Core 能針對不可為 NULL 的 FK 和多對多關系啟用級聯(lián)刪除。 級聯(lián)刪除可能導致形成循環(huán)級聯(lián)刪除規(guī)則。 循環(huán)級聯(lián)刪除規(guī)則會在添加遷移時引發(fā)異常。
例如,如果未將 Department.InstructorID 屬性定義為可以為 NULL:
如果業(yè)務規(guī)則要求 InstructorID 屬性不可為 NULL,請使用以下 Fluent API 語句:
C#
modelBuilder.Entity<Department>()
.HasOne(d => d.Administrator)
.WithMany()
.OnDelete(DeleteBehavior.Restrict)
上面的代碼會針對“系-講師”關系禁用級聯(lián)刪除。
一份注冊記錄面向一名學生所注冊的一門課程。
用以下代碼更新 Models/Enrollment.cs:
C#
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; }
}
}
FK 屬性和導航屬性可反映以下關系:
注冊記錄面向一門課程,因此存在 CourseID FK 屬性和 Course 導航屬性:
C#
public int CourseID { get; set; }
public Course Course { get; set; }
一份注冊記錄針對一名學生,因此存在 StudentID FK 屬性和 Student 導航屬性:
C#
public int StudentID { get; set; }
public Student Student { get; set; }
Student 和 Course 實體之間存在多對多關系。 Enrollment 實體充當數(shù)據(jù)庫中“具有有效負載”的多對多聯(lián)接表。 “具有有效負載”表示 Enrollment 表除了聯(lián)接表的 FK 外還包含其他數(shù)據(jù)(本教程中為 PK 和 Grade)。
下圖顯示這些關系在實體關系圖中的外觀。 (此關系圖是使用適用于 EF 6.X 的 EF Power Tools 生成的。 本教程不介紹如何創(chuàng)建此關系圖。)
每條關系線的一端顯示 1,另一端顯示星號 (*),這表示一對多關系。
如果 Enrollment 表不包含年級信息,則它只需包含兩個 FK(CourseID 和 StudentID)。 無有效負載的多對多聯(lián)接表有時稱為純聯(lián)接表 (PJT)。
Instructor 和 Course 實體具有使用純聯(lián)接表的多對多關系。
注意:EF 6.x 支持多對多關系的隱式聯(lián)接表,但 EF Core 不支持。 有關詳細信息,請參閱 EF Core 2.0 中的多對多關系。
用以下代碼創(chuàng)建 Models/CourseAssignment.cs:
C#
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; }
}
}
講師-課程的多對多關系:
常規(guī)做法是將聯(lián)接實體命名為 EntityName1EntityName2。 例如,使用此模式的“講師-課程”聯(lián)接表是 CourseInstructor。 但是,我們建議使用可描述關系的名稱。
數(shù)據(jù)模型開始時很簡單,其內(nèi)容會逐漸增加。 無有效負載聯(lián)接 (PJT) 通常會發(fā)展為包含有效負載。 該名稱以描述性實體名稱開始,因此不需要隨聯(lián)接表更改而更改。 理想情況下,聯(lián)接實體在業(yè)務領域中可能具有專業(yè)名稱(可能是一個詞)。 例如,可以使用名為“閱讀率”的聯(lián)接實體鏈接“書籍”和“讀客”。 對于“講師-課程”的多對多關系,使用 CourseAssignment 比使用CourseInstructor更合適。
FK 不能為 NULL。 CourseAssignment 中的兩個 FK(InstructorID 和 CourseID)共同唯一標識 CourseAssignment 表的每一行。 CourseAssignment 不需要專用的 PK。 InstructorID 和 CourseID屬性充當組合 PK。 使用 Fluent API 是向 EF Core 指定組合 PK 的唯一方法。 下一部分演示如何配置組合 PK。
組合鍵可確保:
Enrollment 聯(lián)接實體定義其自己的 PK,因此可能會出現(xiàn)此類重復。 若要防止此類重復:
將以下突出顯示的代碼添加到 Data/SchoolContext.cs:
C#
using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity.Models
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}
public DbSet<Course> Courses { get; set; }
public DbSet<Enrollment> Enrollment { get; set; }
public DbSet<Student> Student { 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 });
}
}
}
上面的代碼添加新實體并配置 CourseAssignment 實體的組合 PK。
上面代碼中的 OnModelCreating 方法使用 Fluent API 配置 EF Core 行為。 API 稱為“Fluent”,因為它通常在將一系列方法調(diào)用連接成單個語句后才能使用。 下面的代碼是 Fluent API 的示例:
C#
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Property(b => b.Url)
.IsRequired();
}
在本教程中,F(xiàn)luent API 僅用于不能通過特性完成的數(shù)據(jù)庫映射。 但是,F(xiàn)luent API 可以指定可通過特性完成的大多數(shù)格式設置、驗證和映射規(guī)則。
MinimumLength 等特性不能通過 Fluent API 應用。 MinimumLength 不會更改架構(gòu),它僅應用最小長度驗證規(guī)則。
某些開發(fā)者傾向于僅使用 Fluent API 以保持實體類的“純凈”。 特性和 Fluent API 可以相互混合。 某些配置只能通過 Fluent API 完成(指定組合 PK)。 有些配置只能通過特性完成 (MinimumLength)。使用 Fluent API 或特性的建議做法:
本教程中使用的某些特性可用于:
有關特性和 Fluent API 的詳細信息,請參閱配置方法。
下圖顯示 EF Power Tools 針對已完成的學校模型創(chuàng)建的關系圖。
上面的關系圖顯示:
更新 Data/DbInitializer.cs 中的代碼:
C#
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.Student.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.Student.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.Enrollment.Where(
s =>
s.Student.ID == e.StudentID &&
s.Course.CourseID == e.CourseID).SingleOrDefault();
if (enrollmentInDataBase == null)
{
context.Enrollment.Add(e);
}
}
context.SaveChanges();
}
}
}
前面的代碼為新實體提供種子數(shù)據(jù)。 大多數(shù)此類代碼會創(chuàng)建新實體對象并加載示例數(shù)據(jù)。 示例數(shù)據(jù)用于測試。 有關如何對多對多聯(lián)接表進行種子設定的示例,請參閱 Enrollments 和 CourseAssignments。
生成項目。
Add-Migration ComplexDataModel
前面的命令顯示可能存在數(shù)據(jù)丟失的相關警告。
text
An operation was scaffolded that may result in the loss of data.
Please review the migration for accuracy.
Done. To undo this action, use 'ef migrations remove'
如果運行 database update 命令,則會生成以下錯誤:
text
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'.
現(xiàn)已有一個數(shù)據(jù)庫,需要考慮如何將未來的更改應用到其中。 本教程演示兩種方法:
已更新 DbInitializer 中的代碼將為新實體添加種子數(shù)據(jù)。 若要強制 EF Core 創(chuàng)建新的 DB,請刪除并更新 DB:
在“包管理器控制臺”(PMC) 中運行以下命令:
Drop-Database
Update-Database
從 PMC 運行 Get-Help about_EntityFrameworkCore
,獲取幫助信息。
運行應用。 運行應用后將運行 DbInitializer.Initialize 方法。 DbInitializer.Initialize 將填充新數(shù)據(jù)庫。
在 SSOX 中打開數(shù)據(jù)庫:
查看 CourseAssignment 表:
本部分是可選的。 只有當跳過之前的刪除并重新創(chuàng)建數(shù)據(jù)庫部分時才可以執(zhí)行上述步驟。
當現(xiàn)有數(shù)據(jù)與遷移一起運行時,可能存在不滿足現(xiàn)有數(shù)據(jù)的 FK 約束。 使用生產(chǎn)數(shù)據(jù)時,必須采取步驟來遷移現(xiàn)有數(shù)據(jù)。 本部分提供修復 FK 約束沖突的示例。 務必在備份后執(zhí)行這些代碼更改。 如果已完成上述部分并更新數(shù)據(jù)庫,則不要執(zhí)行這些代碼更改。
{timestamp}_ComplexDataModel.cs 文件包含以下代碼:
C#
migrationBuilder.AddColumn<int>(
name: "DepartmentID",
table: "Course",
type: "int",
nullable: false,
defaultValue: 0);
上面的代碼將向 Course 表添加不可為 NULL 的 DepartmentID FK。 前面教程中的數(shù)據(jù)庫在 Course中包含行,以便遷移時不會更新表。
若要使 ComplexDataModel 遷移可與現(xiàn)有數(shù)據(jù)搭配運行:
更新 ComplexDataModel 類 Up 方法:
C#
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);
添加以下突出顯示的代碼。 新代碼在 .CreateTable( name: "Department" 塊后:
C#
migrationBuilder.CreateTable(
name: "Department",
columns: table => new
{
DepartmentID = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
Budget = table.Column<decimal>(type: "money", nullable: false),
InstructorID = table.Column<int>(type: "int", nullable: true),
Name = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
StartDate = table.Column<DateTime>(type: "datetime2", 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);
經(jīng)過上面的更改,Course 行將在 ComplexDataModel Up 方法運行后與“臨時”系建立聯(lián)系。
生產(chǎn)應用可能:
更多建議: