Contoso 大學(xué)示例 Web 應(yīng)用程序演示如何使用實(shí)體框架(EF)Core 2.0 和 Visual Studio 2017 創(chuàng)建 ASP.NET Core 2.0 MVC Web 應(yīng)用程序祈远。 如欲了解更多本教程相關(guān)信息媳板,請(qǐng)參閱 一燃领、入門
在前面的教程中赔桌,您使用由三個(gè)實(shí)體組成的簡(jiǎn)單數(shù)據(jù)模型。 在本章中君账, 您將添加多個(gè)實(shí)體和關(guān)系宴合,并將通過指定格式化、驗(yàn)證以及數(shù)據(jù)庫(kù)映射規(guī)則來(lái)自定義數(shù)據(jù)模型输吏。
完成時(shí),實(shí)體類構(gòu)成的完整數(shù)據(jù)模型如下圖所示:
使用特性自定義數(shù)據(jù)模型
在本節(jié)中替蛉,你將了解如何通過使用指定格式設(shè)置贯溅,驗(yàn)證和數(shù)據(jù)庫(kù)的映射規(guī)則的屬性來(lái)自定義數(shù)據(jù)模型。 然后在接下來(lái)的幾節(jié)中躲查,通過添加特性到類中它浅,以及添加新的類來(lái)完成完整的 School 數(shù)據(jù)模型。
DataType
特性(attribute)
對(duì)于學(xué)生注冊(cè)日期镣煮,目前所有的網(wǎng)頁(yè)同時(shí)顯示日期和時(shí)間姐霍,雖然您關(guān)注的只是日期。通過使用數(shù)據(jù)注解特性典唇,您可以只在一個(gè)地方進(jìn)行代碼更改镊折,然后所有顯示注冊(cè)日期的格式將得到修正。 為了演示如何達(dá)成此目的蚓聘,您將在 Student
類的 EnrollementDate
屬性上添加一個(gè) Attribute
(特性)腌乡。
在 Models/Student.cs
中,添加 System.ComponentModel.DataAnnotations
命名空間的引用夜牡,并在 EnrollmentDate
屬性上添加 DataType
和 DisplayFormat
特性与纽,如下代碼所示:
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
特性用于指定更為準(zhǔn)確的數(shù)據(jù)庫(kù)類型。在這個(gè)例子中塘装,我們只想跟蹤日期急迂,而不是日期和時(shí)間。 DataType 枚舉中包含許多數(shù)據(jù)類型蹦肴,例如 Date, Time, PhoneNumber, Currency, EmailAddress, 等等僚碎。應(yīng)用程序還可通過 DataType 特性自動(dòng)提供類型特定的功能。例如阴幌,可以為 DataType.EmailAddress 創(chuàng)建 mailto: 鏈接勺阐,并且可以在支持 HTML5 的瀏覽器中為 DataType.Date 提供日期選擇器。 DataType 特性生成 HTML5 瀏覽器可以理解的 HTML 5 data- (發(fā)音為 data dash) 矛双。 DataType 特性不提供任何驗(yàn)證渊抽。
DataType.Date 不指定顯示日期的格式。 默認(rèn)情況下议忽,數(shù)據(jù)字段基于服務(wù)器 CultureInfo 的默認(rèn)格式顯示懒闷。
DisplayFormat 特性用于顯式指定日期格式:
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
ApplyFormatInEditMode
設(shè)置在文本框編輯模式下也應(yīng)用數(shù)據(jù)格式。(有時(shí)候您可能不愿意在某些字段上實(shí)現(xiàn) -- 例如,對(duì)于貨幣值愤估,您可能不想在文本框編輯模式下出現(xiàn)貨幣符號(hào)帮辟。)
您可以單獨(dú)使用 DisplayFormat
特性, 但通常情況下玩焰,同時(shí)使用 DataType
比較好由驹。 DataType
特性傳達(dá)的是數(shù)據(jù)的語(yǔ)義而不是如何在屏幕上顯示,并提供了您無(wú)法從使用 Displayformat
中得到的好處:
- 瀏覽器可以啟用 HTML5 功能 (例如顯示一個(gè)日歷控件昔园、 區(qū)域設(shè)置對(duì)應(yīng)的貨幣符號(hào)荔棉、 電子郵件鏈接,某些客戶端輸入驗(yàn)證蒿赢,等等。)渣触。
- 默認(rèn)情況下羡棵,瀏覽器將根據(jù)區(qū)域設(shè)置采用正確的格式呈現(xiàn)數(shù)據(jù)。
有關(guān)詳細(xì)信息嗅钻,請(qǐng)參閱 <input> tag helper documentation 皂冰。
運(yùn)行應(yīng)用程序,轉(zhuǎn)到 Student Index 頁(yè)养篓,可以看到秃流,Enrollment Date 字段不再顯示時(shí)間。 其他使用 Student 模型的視圖也一樣柳弄。
StringLength
特性
您還可以通過使用特性來(lái)指定數(shù)據(jù)驗(yàn)證規(guī)則及驗(yàn)證錯(cuò)誤消息舶胀。 StringLength
特性設(shè)置在數(shù)據(jù)庫(kù)中保存的最大長(zhǎng)度,并為 ASP.NET MVC 應(yīng)用提供客戶端和服務(wù)端驗(yàn)證碧注。 您還可以在這個(gè)特性中指定最小字符串長(zhǎng)度嚣伐,但最小值對(duì)數(shù)據(jù)庫(kù)結(jié)構(gòu)沒有任何影響。
現(xiàn)在假設(shè)您想確保用戶不能在名字中輸入超過50個(gè)字符萍丐。 為了添加此限制轩端, 在 LastName
和 FirstMidName
屬性上添加 StringLength
特性,如下所示:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
<span style="background-color: #FF0;">[StringLength(50)]</span>
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; }
}
}
StringLength
特性并無(wú)法阻止用戶在名字中輸入空格逝变』穑可以使用 RegularExpress
(正則表達(dá)式)特性來(lái)限制輸入。例如壳影,下面的代碼要求第一個(gè)字符是大寫且其余的字符都是字母:
[RegularExpression(@"^[A-Z]+[a-zA-Z''-'\s]*$")]
MaxLength
特性提供了類似于 StringLength
特性的功能拱层,但也沒有提供客戶端驗(yàn)證功能。
現(xiàn)在态贤,數(shù)據(jù)模型已更改舱呻,并需要將此更改反映到數(shù)據(jù)庫(kù)結(jié)構(gòu)中。 您將使用遷移來(lái)更新數(shù)據(jù)庫(kù)結(jié)構(gòu),同時(shí)不會(huì)丟失在使用應(yīng)用程序過程中已經(jīng)輸入數(shù)據(jù)庫(kù)的數(shù)據(jù)箱吕。
保存所做的更改并生成項(xiàng)目芥驳。 然后在項(xiàng)目文件夾中打開命令窗口并輸入以下命令:
dotnet ef migrations add MaxLengthOnNames
dotnet ef database update
migrations add
命令警告說可能發(fā)生數(shù)據(jù)丟失,因?yàn)楦膶?dǎo)致兩個(gè)數(shù)據(jù)列的最大長(zhǎng)度變短茬高。 遷移功能創(chuàng)建了一個(gè)名為 <時(shí)間戳>_MaxLengthOnNames.cs
的文件兆旬。 此文件中的 Up
方法將會(huì)更新數(shù)據(jù)庫(kù)結(jié)構(gòu),以匹配當(dāng)前數(shù)據(jù)模型怎栽。 database update
命令負(fù)責(zé)執(zhí)行此代碼丽猬。
Entity Framework 使用遷移文件名前的時(shí)間戳對(duì)遷移進(jìn)行排序。 你可以在運(yùn)行 update-database 命令前, 創(chuàng)建多個(gè)遷移熏瞄,這樣所有的遷移將按照創(chuàng)建順序依次應(yīng)用脚祟。
運(yùn)行應(yīng)用,轉(zhuǎn)至 Student 頁(yè)面强饮,點(diǎn)擊 Create New 由桌, 并在 First Name 、Last Name 中輸入超過 50 個(gè)字符邮丰。 當(dāng)你單擊創(chuàng)建行您,客戶端驗(yàn)證顯示一條錯(cuò)誤消息。
Column
特性
特性還可用于控制如何類和屬性映射到數(shù)據(jù)庫(kù)。假設(shè)你已將 FirstMidName 用于 first-name 字段(因?yàn)樽侄沃羞€包含 middle name)。但同時(shí)你又希望數(shù)據(jù)庫(kù)中的列名為 FirstName黔牵, 因?yàn)榫帉戓槍?duì)數(shù)據(jù)庫(kù)查詢的用戶習(xí)慣于該名稱。 使用 Column
特性可以達(dá)成此映射要求捌斧。
Column
特性指定當(dāng)創(chuàng)建數(shù)據(jù)庫(kù)時(shí),映射到 Student表的 FirstMidName 屬性將被命名為 FirstName泉沾。 換句話說骤星, 當(dāng)你的代碼引用 Student.FirstMidName時(shí), 數(shù)據(jù)讀和寫都是發(fā)生在 Student 表的 FirstName 列爆哑。 如果未指定列名稱洞难,系統(tǒng)使用屬性名作為列名。
在 Student.cs 文件中揭朝, 添加 System.ComponentModel.DataAnnotations.Schema 命名空間引用队贱,并在 FirstMidName 屬性添加 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, 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; }
}
}
Column
特性的添加改變了 SchoolContext 的模型潭袱,導(dǎo)致與數(shù)據(jù)庫(kù)不再匹配柱嫌。
保存所做的更改并生成項(xiàng)目。 然后在項(xiàng)目文件夾中打開命令窗口并輸入以下命令以創(chuàng)建另一個(gè)遷移:
dotnet ef migrations add ColumnFirstName
dotnet ef database update
在 SQL Server 對(duì)象資源管理器屯换,通過雙擊 Student 表打開 Student 表設(shè)計(jì)視圖编丘。
應(yīng)用前兩個(gè)遷移之前与学,F(xiàn)irstName 和 LastName 列為 nvarchar (max) 類型。 而現(xiàn)在是 nvarchar(50) 類型嘉抓,并且相應(yīng)的列從 FirstMidName 更改為 FirstName索守。
備注
如果您完成下列部分中創(chuàng)建的所有實(shí)體類之前嘗試編譯,可能會(huì)收到編譯器錯(cuò)誤抑片。
最終的 Student 實(shí)體更改
在 Models/Student.cs 文件中卵佛,替換早期代碼為如下代碼。
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; }
<span style="background-color: #FF0;">[Required]</span>
[StringLength(50)]
[Display(Name = "Last Name")]
public string LastName { get; set; }
<span style="background-color: #FF0;">[Required]</span>
[StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
[Column("FirstName")]
<span style="background-color: #FF0;">[Display(Name = "First Name")]</span>
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
<span style="background-color: #FF0;">[Display(Name = "Enrollment Date")]</span>
public DateTime EnrollmentDate { get; set; }
<span style="background-color: #FF0;">[Display(Name = "Full Name")]
public string FullName
{
get
{
return LastName + ", " + FirstMidName;
}
}</span>
public ICollection<Enrollment> Enrollments { get; set; }
}
}
Required
特性
Required
特性使得名字屬性成為必填字段敞斋。 無(wú)需為不可為空類型指定 Required
特性截汪,如值類型(Datetime, int, double, float 等。)植捎。 不可為空類型自動(dòng)被視為必填字段衙解。
您也可以移除 Require
特性,并代之以 StringLenght
特性中的一個(gè)最小長(zhǎng)度參數(shù)焰枢。
[Display(Name = "Last Name")]
[StringLength(50, MinimumLength=1)]
public string LastName { get; set; }
Display
特性
Display
特性指定文本框的標(biāo)題應(yīng)為 "First Name", "Last Name", "Full Name" 及 "Enrollment Date" 而不是使用屬性名丢郊。(屬性名單詞中沒有空格)。
FullName 計(jì)算屬性
FullName 是一個(gè)計(jì)算屬性医咨, 由兩個(gè)其他屬性合并返回一個(gè)值。 因此它僅有一個(gè) get 訪問器架诞,數(shù)據(jù)庫(kù)中不會(huì)有 FullName 列生成拟淮。
創(chuàng)建 Instructor 實(shí)體
創(chuàng)建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; }
}
}
注意到在 Student 和 Instructor 實(shí)體中有多個(gè)相同的屬性谴忧。 在本系列教程的 [Implementing Inheritance] 中很泊,您將重構(gòu)此代碼以消除冗余。
多個(gè)特性可以放在一行上沾谓, 因此你可以像下面這樣寫 HireDate 的特性委造。
[DataType(DataType.Date),Display(Name = "Hire Date"),DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
CourseAssignments 和 OfficeAssignment 導(dǎo)航屬性
CourseAssignments
和 OfficeAssignment
屬性是導(dǎo)航屬性。
一個(gè) Instructor (教師)可以教授任意數(shù)量的 Course (課程)均驶,因此 CourseAssignments
定義為集合昏兆。
public ICollection<CourseAssignment> CourseAssignments { get; set; }
如果導(dǎo)航屬性可以包含多個(gè)實(shí)體,其類型必須是一個(gè)可以添加妇穴、刪除和更新的實(shí)體列表爬虱。 您可以指定為 ICollection<T> 、List<T> 或者 HashSet<T> 類型腾它。 如果您指定為 ICollection<T> 類型跑筝, EF 默認(rèn)創(chuàng)建為一個(gè) HashSet<T> 集合類型。
關(guān)于為何這是 CouseAssignment 實(shí)體集合瞒滴,將在下面的多對(duì)多關(guān)系一節(jié)中詳細(xì)闡述曲梗。
Contoso 大學(xué)的業(yè)務(wù)規(guī)則指出,一個(gè) Instructor (教師)只能有最多一個(gè) office (辦公室),因此 OfficeAssignment 屬性只包含一個(gè) OfficeAssignment 實(shí)體 (在未分配辦公室的情況下也可能為空)虏两。
public OfficeAssignment OfficeAssignment { get; set; }
創(chuàng)建 OfficeAssignment 實(shí)體
創(chuàng)建 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
特性
在 Instructor 和 OfficeAssignment 實(shí)體間有一個(gè) “一 對(duì) 零或一” 的關(guān)系愧旦。 一個(gè) OfficeAssignment 實(shí)體只與分配給的 Instructor 有關(guān)系,因此其主鍵也同時(shí)是連接到 Instructor 實(shí)體的外鍵碘举。 但是忘瓦, Entity Framework 無(wú)法自動(dòng)識(shí)別 InstructorID 作為此實(shí)體的主鍵,因?yàn)槠涿Q未遵循 ID 或 <類名>ID 的命名約定引颈。 因此耕皮,我們使用 Key
特性來(lái)標(biāo)識(shí) InstructorID 作為主鍵。
[Key]
public int InstructorID { get; set; }
如果實(shí)體已經(jīng)有主鍵蝙场,但你想要用不同于 <類名>ID 或 ID 的屬性名的時(shí)候凌停,你也可以使用 Key
特性。
默認(rèn)情況下售滤, EF 將 Key
處理為 non-database-generated (非數(shù)據(jù)庫(kù)生成)罚拟,因?yàn)榇肆惺怯糜谧R(shí)別關(guān)系。
Instructor
導(dǎo)航屬性
Instructor
實(shí)體有一個(gè)可為空的 OfficeAssignment 導(dǎo)航屬性(因?yàn)榻處熆赡軟]有分配一個(gè)辦公室)完箩, OfficeAssignment
實(shí)體有一個(gè)不可為空的 Instructor
導(dǎo)航屬性(當(dāng)沒有教師的時(shí)候赐俗,一個(gè) OfficeAssignment - 辦公室分配不可能存在 -- InstructorID 不可為空)。 當(dāng) Instructor 實(shí)體具有相關(guān)的 OfficeAssignment 實(shí)體時(shí)弊知,兩個(gè)實(shí)體都有一個(gè)指向?qū)Ψ降膶?dǎo)航屬性阻逮。
您可以在 Instructor
導(dǎo)航屬性前加一個(gè) Required
特性以指定必須有相關(guān)的 Instructor , 但實(shí)際無(wú)需這樣做秩彤,因?yàn)?InstructorID 外鍵 (同時(shí)也是此表的主鍵)本就是不可為空的叔扼。
修改 Course 實(shí)體
在 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; }
}
}
Course
實(shí)體有一個(gè) DepartmentID 外鍵屬性和一個(gè) Department 導(dǎo)航屬性用于指向相關(guān)的 Department 實(shí)體瓜富。
當(dāng)您已經(jīng)擁有相關(guān)實(shí)體的導(dǎo)航屬性時(shí), Entity Framework 不強(qiáng)求你一定要添加一個(gè)外鍵屬性到數(shù)據(jù)模型中降盹。 EF 可以在需要的時(shí)候自動(dòng)在數(shù)據(jù)庫(kù)中創(chuàng)建外鍵及外鍵的影子屬性与柑。 但是,數(shù)據(jù)模型中具有外鍵可以使更新更簡(jiǎn)單蓄坏、 更高效仅胞。例如,當(dāng)您提取一個(gè) Course 實(shí)體用于編輯時(shí)剑辫, 如果你沒有聲明加載的話干旧,Department 實(shí)體是空的,當(dāng)您要更新 Course 實(shí)體時(shí)妹蔽,您將需要先讀取 Department 實(shí)體椎眯。 如果外鍵屬性 DepartmentID 包含在數(shù)據(jù)模型中挠将,則您無(wú)需在更新前讀取 Department 實(shí)體。
DatabaseGenerated
特性
在 CourseID 屬性上標(biāo)注的 DatabaseGenerated
特性與 None
參數(shù)指定主鍵值有用戶而不是數(shù)據(jù)庫(kù)生成编整。
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }
默認(rèn)情況下舔稀,實(shí)體框架假定主鍵值都由數(shù)據(jù)庫(kù)生成。 這是大部分情況下您希望的掌测。但是内贮,對(duì)于 Course 實(shí)體, 您將使用一個(gè)用戶指定的課程號(hào)碼汞斧,比如 1000 系列用于一個(gè)部門夜郁,2000 系列用于另外一個(gè)部門,依此類推粘勒。
DatabaseGenerated
特性也可用于生成默認(rèn)值竞端, 比如在數(shù)據(jù)庫(kù)中用于記錄數(shù)據(jù)行創(chuàng)建或更新日期的列。有關(guān)詳細(xì)信息庙睡,請(qǐng)參閱 Generated Properties 事富。
外鍵和導(dǎo)航屬性
Course 實(shí)體中的外鍵屬性和導(dǎo)航屬性反映了以下關(guān)系:
一個(gè)課程將分配到一個(gè)部門,如上面提到的一樣乘陪,這兒有一個(gè) DepartmentID 外鍵和一個(gè) Department 導(dǎo)航屬性统台。
public int DepartmentID { get; set; }
public Department Department { get; set; }
一個(gè)課程可以有任意多的學(xué)生注冊(cè), 因此 Enrollments 導(dǎo)航屬性是個(gè)集合:
public ICollection<Enrollment> Enrollments { get; set; }
一個(gè)課程可能由多個(gè)教師執(zhí)教啡邑,因此 CourseAssignments 導(dǎo)航屬性是個(gè)集合(CourseAssignment 類型稍后解釋)贱勃。
public ICollection<CourseAssignment> CourseAssignments { get; set; }
創(chuàng)建 Department 實(shí)體
創(chuàng)建 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 特性更改列名映射。在 Department 實(shí)體代碼中谣拣, Column 特性用于更改 SQL 數(shù)據(jù)類型映射,以便此列在數(shù)據(jù)庫(kù)中使用 SQL Server 的 money 類型族展。
[Column(TypeName="money")]
public decimal Budget { get; set; }
列映射通常并無(wú)必要森缠, 因?yàn)?Entity Framework 會(huì)基于您給屬性定義的 CLR 類型,為您選擇適合的 SQL Server 數(shù)據(jù)類型仪缸。 CLR decimal 類型映射到 SQL Server 的 deccimal 類型贵涵。 不過,本例中恰画,您指定此列將用于保存貨幣金額宾茂,money 數(shù)據(jù)類型更為合適。
外鍵和導(dǎo)航屬性
外鍵和導(dǎo)航屬性反映了以下關(guān)系:
一個(gè)部門可以有或者沒有管理員拴还,一個(gè)管理員總是一個(gè)教師跨晴。因此 InstructorID 屬性用于指向 Instructor 實(shí)體的外鍵,int
后面的 ?
表示這個(gè)屬性是可為空屬性片林。 導(dǎo)航屬性命名為 Administrator 但實(shí)際上包含的是 Instructor 實(shí)體端盆。
public int? InstructorID { get; set; }
public Instructor Administrator { get; set; }
一個(gè)部門可能有多個(gè)課程怀骤,因此有一個(gè) Course 導(dǎo)航屬性:
public ICollection<Course> Courses { get; set; }
備注
按照約定,Entity Framework 對(duì)不為空外鍵和多對(duì)多關(guān)系實(shí)施級(jí)聯(lián)刪除焕妙。 這可能導(dǎo)致循環(huán)的級(jí)聯(lián)刪除規(guī)則蒋伦,當(dāng)您添加一個(gè)遷移時(shí),會(huì)引發(fā)異常焚鹊。 例如痕届,如果您定義 Department.InstructorID 屬性可為空, EF 會(huì)配置一個(gè)級(jí)聯(lián)刪除規(guī)則末患,用于刪除部門研叫, 而這并不是您所希望發(fā)生的。 如果您的業(yè)務(wù)規(guī)則要求 InstructorID 屬性不可為空阻塑,那么您必須使用如下的 fluent API 語(yǔ)句來(lái)禁用關(guān)系上的級(jí)聯(lián)刪除規(guī)則蓝撇。
modelBuilder.Entity<Department>() .HasOne(d => d.Administrator) .WithMany() .OnDelete(DeleteBehavior.Restrict)
修改 Enrollment 實(shí)體
在 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; }
}
}
外鍵和導(dǎo)航屬性
外鍵屬性和導(dǎo)航屬性反映了以下關(guān)系:
一個(gè)注冊(cè)記錄只對(duì)應(yīng)一個(gè)課程陈莽, 因此這兒有一個(gè) CourseID 外鍵屬性和一個(gè) Course 導(dǎo)航屬性渤昌。
public int CourseID { get; set; }
public Course Course { get; set; }
一個(gè)注冊(cè)記錄只對(duì)應(yīng)一個(gè)學(xué)生, 因此這兒有一個(gè) StudentID 外鍵屬性和一個(gè) Student 導(dǎo)航屬性走搁。
public int StudentID { get; set; }
public Student Student { get; set; }
多對(duì)多關(guān)系
在 Student 和 Course 實(shí)體間独柑,存在著一個(gè)多對(duì)多的關(guān)系, Enrollment 實(shí)體作為一個(gè)多對(duì)多的關(guān)聯(lián)表在數(shù)據(jù)庫(kù)中承載關(guān)系及相關(guān)信息私植。承載相關(guān)信息意味著 Enrollment 數(shù)據(jù)表除了包含關(guān)聯(lián)表的外鍵之外忌栅,還包含其他數(shù)據(jù)。(在這里曲稼,包含一個(gè)主鍵和一個(gè)年級(jí)屬性)
下圖在一個(gè)實(shí)體關(guān)系圖表中展示這些關(guān)系索绪。(這個(gè)關(guān)系圖表使用 Entity Framework Power Tools for EF 6.x 生成; 創(chuàng)建關(guān)系圖表不在本教程范圍內(nèi)贫悄,此處關(guān)系圖表僅用于展示用途瑞驱。)
每一條關(guān)系連線都有一端顯示
1
和另外一端顯示 *
,以表明這是一個(gè)一對(duì)多的關(guān)系窄坦。假設(shè) Enrollment 表不包括年級(jí)信息唤反,只需要包含兩個(gè)外鍵 CourseID 和 StudentID 。 在這種情況下鸭津, 它將是一個(gè)沒有有效負(fù)載的多對(duì)多關(guān)聯(lián)表(或者說是一個(gè)純粹的關(guān)聯(lián)表)彤侍。 Instructor 和 Course 實(shí)體就具有這種多對(duì)多關(guān)系, 您下一步就將創(chuàng)建一個(gè)沒有有效負(fù)載的實(shí)體類充當(dāng)關(guān)聯(lián)表逆趋。
(EF 6.x 支持隱式多對(duì)多關(guān)聯(lián)表盏阶,但 EF Core 不支持。 有關(guān)詳細(xì)信息闻书,請(qǐng)參閱 discussion in the EF Core GitHub repository 般哼。)
CourseAssignment
實(shí)體
使用如下代碼創(chuàng)建 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; }
}
}
關(guān)聯(lián)實(shí)體名稱
在數(shù)據(jù)庫(kù)中需要有一個(gè)關(guān)聯(lián)表用于 Instructor <-> Courses 的多對(duì)多關(guān)系吴汪, 并且以實(shí)體集合方式體現(xiàn)。 常規(guī)做法是命名一個(gè)關(guān)聯(lián)實(shí)體 EntityName1EntityName2蒸眠, 在這里就是 CourseInstructor 漾橙。 但是, 我們建議您選擇一個(gè)可以描述這種關(guān)系的名稱楞卡。 數(shù)據(jù)模型一開始總是簡(jiǎn)單的霜运,然后逐步增長(zhǎng),從不需要有效負(fù)載的純關(guān)聯(lián)表到需要有效負(fù)載蒋腮。理想狀態(tài)下淘捡,關(guān)聯(lián)實(shí)體將在業(yè)務(wù)領(lǐng)域中擁有其自然名稱。例如池摧,書籍(Books)和客戶(Customers)之間可以通過評(píng)分(Ratings)進(jìn)行關(guān)聯(lián)焦除。 對(duì)于當(dāng)前這個(gè)關(guān)系, CourseAssignment 是一個(gè)比 CourseInstructor 更好的選擇作彤。
復(fù)合鍵
由于外鍵不可為空膘魄,并且唯一的標(biāo)識(shí)每一個(gè)數(shù)據(jù)行,這兒無(wú)需一個(gè)另外的主鍵竭讳。 InstructorID 和 CourseID 屬性可以充當(dāng)復(fù)合主鍵创葡。 在 EF 中標(biāo)識(shí)復(fù)合主鍵的唯一方式是使用 Fluent API 方式 (無(wú)法通過特性標(biāo)注實(shí)現(xiàn))。在下一節(jié)中您將看到如何配置復(fù)合主鍵绢慢。
復(fù)合主鍵確保了當(dāng)你有多個(gè)行對(duì)應(yīng)一個(gè)課程灿渴,以及多個(gè)行對(duì)應(yīng)一個(gè)教師,同時(shí)又可以避免出現(xiàn)多個(gè)行對(duì)應(yīng)同一個(gè)教師同一個(gè)課程的情況出現(xiàn)胰舆。 Enrollment 關(guān)聯(lián)實(shí)體定義了自身的主鍵骚露, 因此可能會(huì)出現(xiàn)這種重復(fù)項(xiàng)。 若要防止此類重復(fù)的存在缚窿,您可以在外鍵字段上添加唯一索引棘幸,或者配置 Enrollment 使用類似于 CourseAssignment 的復(fù)合主鍵。 有關(guān)詳細(xì)信息滨攻,請(qǐng)參閱 Index (索引) 够话。
更新數(shù)據(jù)庫(kù)上下文
在 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");
<span style="background-color: #FF0;">
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 });</span>
}
}
}
這些代碼添加了新的實(shí)體光绕,并配置 CourseAssignment 實(shí)體的復(fù)合主鍵。
使用 Fluent API 代替 attributes (特性)
DbContext 類中的 OnModelCreating 方法使用 fluent API 配置 EF 的行為畜份。 之所以將這些 API 稱為 fluent(流暢的)诞帐,因?yàn)橥ǔS糜趯⒍鄠€(gè)方法連接成為一條語(yǔ)句,比如下面這個(gè)來(lái)自 EF Core documentation 的例子:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Property(b => b.Url)
.IsRequired();
}
在本教程中爆雹, 僅為不能使用特性標(biāo)注的數(shù)據(jù)庫(kù)映射使用 fluent API 停蕉。然而愕鼓, 您也可以用 fluent API 指定大部分您可以使用特性實(shí)現(xiàn)的格式,驗(yàn)證和映射規(guī)則慧起。 某些特性菇晃,如 MinimumLength 不能使用 fluent API 設(shè)定。 如前所述蚓挤, MinimumLength 不會(huì)修改架構(gòu)磺送,而僅用于客戶端和服務(wù)端驗(yàn)證規(guī)則。
有些開發(fā)人員中意排他的使用 fluent API 方式灿意,這樣可以保持實(shí)體類干凈估灿。 如果您愿意,可以混合使用特性和 fluent API 方式缤剧, 只有少數(shù)自定義項(xiàng)只能使用 fluent API 完成馅袁,但通常的做法是選擇其中的一種方式,并盡可能貫徹始終荒辕。 如果您同時(shí)使用兩者汗销,請(qǐng)記住,當(dāng)兩者設(shè)置產(chǎn)生沖突的時(shí)候兄纺, fluent API 將會(huì)覆蓋特性的設(shè)置大溜。
有關(guān) attributes vs. fluent API 的更多信息,請(qǐng)參閱 Methods of configuration 估脆。
顯示關(guān)系的實(shí)體圖表
下圖是使用 Entity Framework Power Tools 創(chuàng)建的完整學(xué)校模型的圖表钦奋。
除了一條“一對(duì)多”關(guān)系線外, 您還可以在 Instructor 和 OfficeAssignment 實(shí)體間看到一個(gè)“一對(duì)零或一”關(guān)系線(1 - 0..1)疙赠,以及 Instructor 和 Department 實(shí)體間的“零或一對(duì)多”關(guān)系線(0..1 - *)付材。
給數(shù)據(jù)庫(kù)填充測(cè)試數(shù)據(jù)
使用以下代碼替換 Data/DbInitializer.cs 文件中的代碼,以提供新建實(shí)體的測(cè)試數(shù)據(jù)圃阳。
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();
}
}
}
正如您在第一篇教程中看到的厌衔, 大多數(shù)的代碼只是創(chuàng)建一個(gè)簡(jiǎn)單的實(shí)體對(duì)象,并將數(shù)據(jù)寫入對(duì)應(yīng)的屬性中捍岳。請(qǐng)注意多對(duì)多關(guān)系是如何處理的:代碼通過創(chuàng)建 Enrollments 及 CourseAssignment 關(guān)聯(lián)實(shí)體集合來(lái)創(chuàng)建關(guān)系富寿。
添加遷移
保存所做更改并生成項(xiàng)目。然后在項(xiàng)目文件夾打開命令窗口锣夹,輸入 migrations add 命令(暫時(shí)先別運(yùn)行更新數(shù)據(jù)庫(kù)的命令):
dotnet ef migrations add ComplexDataModel
您會(huì)收到一條可能丟失數(shù)據(jù)的警告消息页徐。
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'
如果您此時(shí)嘗試執(zhí)行數(shù)據(jù)庫(kù)更新命令的話,您會(huì)收到如下錯(cuò)誤消息:
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'.
有時(shí)候银萍,當(dāng)您在已有數(shù)據(jù)上執(zhí)行遷移变勇,為了滿足外鍵約束,您需要在數(shù)據(jù)庫(kù)中插入一些 stub data (存根數(shù)據(jù)贴唇,這個(gè)翻譯并不能讓人滿意搀绣,目前在有關(guān) TDD 測(cè)試驅(qū)動(dòng)開發(fā)中也是用的這個(gè)翻譯)飞袋。 在生成的代碼中, Up 方法在 Course 表中添加了一個(gè)不可為空的 DepartmentID 外鍵链患。如果代碼運(yùn)行時(shí)巧鸭, Course 表中已經(jīng)存在數(shù)據(jù)行,則 AddColumn 操作將會(huì)失敗麻捻,因?yàn)?SQL Server 不知道在那一個(gè)不可為空的列中該放入何值蹄皱。 對(duì)于本教程來(lái)說,您將在一個(gè)新的數(shù)據(jù)庫(kù)上運(yùn)行遷移芯肤,但對(duì)于一個(gè)生產(chǎn)環(huán)境中的應(yīng)用程序來(lái)說巷折,您必須想辦法對(duì)現(xiàn)有數(shù)據(jù)實(shí)現(xiàn)遷移,下面的說明展示一個(gè)如何執(zhí)行該操作的示例崖咨。
要在現(xiàn)有數(shù)據(jù)上讓遷移順利工作锻拘,您必須手工修改代碼以提供新建列一個(gè)默認(rèn)值,創(chuàng)建一個(gè)名為 “temp” 的存根(stub)部門用于默認(rèn)部門击蹲。結(jié)果是署拟,在 Up 方法運(yùn)行后,現(xiàn)有的課程將全部與 “Temp” 部門關(guān)聯(lián)歌豺。
- 打開 {時(shí)間戳}_ComplexDataModel.cs 文件推穷。
- 注釋掉在 Course 表中添加 DepartmentID 列的代碼行。
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);
- 在創(chuàng)建 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);
在生產(chǎn)環(huán)境的應(yīng)用程序中馒铃,您將會(huì)編寫代碼或腳本添加部門及部門關(guān)聯(lián)的課程。然后就不再需要 “Temp” 部門或 Course.DepartmentID 列的默認(rèn)值痕惋。
保存所做的更改并生成項(xiàng)目区宇。
更改連接字符串,并更新數(shù)據(jù)庫(kù)
現(xiàn)在您在 DbInitializer 類中有新代碼用于添加測(cè)試數(shù)據(jù)到一個(gè)空的數(shù)據(jù)庫(kù)值戳。 要讓 EF 創(chuàng)建一個(gè)新的空數(shù)據(jù)庫(kù)议谷, 可以通過修改 appsettings.json 中的連接字符串的數(shù)據(jù)庫(kù)名稱到 ContosoUniversity3 或者其他您的電腦未使用的名稱。
{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=ContosoUniversity3;Trusted_Connection=True;MultipleActiveResultSets=true"
},
保存更改到 appsettings.json 堕虹。
備注
作為對(duì)不斷變化的數(shù)據(jù)庫(kù)名稱的替代方法卧晓,您可以刪除數(shù)據(jù)庫(kù)。 使用SQL Server 對(duì)象資源管理器(SSOX) 或database dropCLI 命令:
dotnet ef database drop
修改數(shù)據(jù)庫(kù)名稱或者刪除數(shù)據(jù)庫(kù)后赴捞, 在命令行窗口運(yùn)行數(shù)據(jù)庫(kù)更新命令以讓遷移生效逼裆。
dotnet ef database update
運(yùn)行應(yīng)用程序,DbInitializer.Initialize 方法運(yùn)行并填充新的數(shù)據(jù)庫(kù)螟炫。
在 SSOX (SQL Server 資源管理器)打開數(shù)據(jù)庫(kù)波附, 展開表節(jié)點(diǎn)以查看是否已創(chuàng)建的所有表艺晴。(如果您的 SSOX 仍舊保留打開狀態(tài)昼钻,則點(diǎn)擊刷新按鈕掸屡。)
運(yùn)行應(yīng)用程序,觸發(fā)初始化代碼并填充測(cè)試數(shù)據(jù)到數(shù)據(jù)庫(kù)然评。
右鍵點(diǎn)擊 CourseAssignment 表仅财,選擇“查看數(shù)據(jù)”來(lái)驗(yàn)證其中確有數(shù)據(jù)。
小結(jié)
現(xiàn)在您有了一個(gè)更復(fù)雜的數(shù)據(jù)模型和對(duì)應(yīng)的數(shù)據(jù)庫(kù)碗淌。在以下教程中盏求,你將了解有關(guān)如何訪問相關(guān)的數(shù)據(jù)的詳細(xì)信息。