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