Working with Data » Getting started with ASP.NET Core and Entity Framework Core using Visual Studio

Creating a complex data model 建立複雜數據模型

8 of 9 people found this helpful

The Contoso University sample web application demonstrates how to create ASP.NET Core 1.0 MVC web applications using Entity Framework Core 1.0 and Visual Studio 2015. For information about the tutorial series, see the first tutorial in the series.html

Contoso 大學示例 web 應用程序演示如何使用EF Core 1.0 和VS2015建立 ASP.NET Core 1.0 MVC web 應用程序系列教程有關信息請參閱系列第一篇教程

In the previous tutorials you worked with a simple data model that was composed of three entities. In this tutorial you’ll add more entities and relationships and you’ll customize the data model by specifying formatting, validation, and database mapping rules.node

之前教程中,你使用簡單數據模型進行工做,其三個實體組成教程中您將添加更多實體關係你會經過指定格式 驗證數據庫映射規則自定義數據模型

When you’re finished, the entity classes will make up the completed data model that’s shown in the following illustration:ios

完成全部工做實體以下

 

Sections:web

Customize the Data Model by Using Attributes 使用Attributes定製數據模型

In this section you’ll see how to customize the data model by using attributes that specify formatting, validation, and database mapping rules. Then in several of the following sections you’ll create the complete School data model by adding attributes to the classes you already created and creating new classes for the remaining entity types in the model.sql

本節中,您會看到如何使用attributes來自定義數據模型,包括指定格式、驗證數據庫映射規則。而後,在後續的幾個章節中您將經過Attributes給已經建立的類和即將創建的類進行定製

The DataType attribute

DateType屬性數據庫

For student enrollment dates, all of the web pages currently display the time along with the date, although all you care about for this field is the date. By using data annotation attributes, you can make one code change that will fix the display format in every view that shows the data. To see an example of how to do that, you’ll add an attribute to the EnrollmentDate property in the Student class.json

對於學生註冊日期,如今全部網頁都是按照日期加時間的格式顯示,可是你關注的僅僅是這個字段的日期。經過使用數據註解(data annotation)屬性,能夠實現經過修改一條代碼而改變全部顯示該項內容的視圖。用一個例子演示如何去作,你將給Student類中的Enrollment屬性添加一個屬性。c#

In Models/Student.cs, add a using statement for the System.ComponentModel.DataAnnotations namespace and add DataType and DisplayFormat attributes to the EnrollmentDate property, as shown in the following example:api

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; }
    }
}

The DataType attribute is used to specify a data type that is more specific than the database intrinsic type. In this case we only want to keep track of the date, not the date and time. The DataType Enumeration provides for many data types, such as Date, Time, PhoneNumber, Currency, EmailAddress, and more. The DataType attribute can also enable the application to automatically provide type-specific features. For example, a mailto: link can be created for DataType.EmailAddress, and a date selector can be provided for DataType.Date in browsers that support HTML5. The DataType attribute emits HTML 5 data- (pronounced data dash) attributes that HTML 5 browsers can understand. The DataType attributes do not provide any validation.瀏覽器

DataType屬性用於指定比數據庫內部類型更具體化的數據類型。在本例中,咱們只想保留日期,而不是日期加時間。DateType枚舉提供許多數據類型,例如:日期、時間、電話號碼、貨幣、電郵地址等等。DataType屬性也能使應用自動提供類型指定功能。例如,DataType.EmailAddress可生成mailto:連接,DataType.Date可向支持HTML5的瀏覽器一個日期選擇器。DataType屬性傳遞給支持HTML5的瀏覽器一個data-(以data-打頭的聲明)。DataType屬性不提供任何驗證功能。

DataType.Date does not specify the format of the date that is displayed. By default, the data field is displayed according to the default formats based on the server’s CultureInfo.

DateType.Date並不指定日期的顯示格式。默認狀況下,顯示的數據根據服務器文化信息(國別)的默認格式進行顯示。

The DisplayFormat attribute is used to explicitly specify the date format:

DisplayFormat屬性用於明確指定日期的格式:

[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]

The ApplyFormatInEditMode setting specifies that the formatting should also be applied when the value is displayed in a text box for editing. (You might not want that for some fields – for example, for currency values, you might not want the currency symbol in the text box for editing.)

ApplyFormatInEditMode設置指定了在文件框的編輯狀態下仍然採用該顯示格式。(你可能不想這樣作,例如:對於貨幣值來講,你可能不想在文本框中進行編輯時仍然顯示貨幣符號)。

You can use the DisplayFormat attribute by itself, but it’s generally a good idea to use the DataType attribute also. The DataType attribute conveys the semantics of the data as opposed to how to render it on a screen, and provides the following benefits that you don’t get with DisplayFormat:

你可僅使用DisplayFormat屬性,可是同時使用DateType屬性一般是個更好的方法。DataType屬性傳遞了數據的語義,(若是僅使用DisplayFormat屬性)則僅僅是說明了如何呈現屏幕上,而且提供了下列優勢,這些優勢從DisplayFormat屬性是的得不到的:

  • The browser can enable HTML5 features (for example to show a calendar control, the locale-appropriate currency symbol, email links, some client-side input validation, etc.).
  • 瀏覽器可使用HTML5特性(例如:顯示一個日曆控件、本地的貨幣符號、email連接、一些客戶端輸入驗證,等等)。
  • By default, the browser will render data using the correct format based on your locale.
  • 默認狀況下,瀏覽器將基於本地狀況設置適當的數據格式。

For more information, see the <input> tag helper documentation.

更多的信息,請參看<input> tag helper documentation.

Run the Students Index page again and notice that times are no longer displayed for the enrollment dates. The same will be true for any view that uses the Student model.

The StringLength attribute

StringLength屬性

You can also specify data validation rules and validation error messages using attributes. The StringLength attribute sets the maximum length in the database and provides client side and server side validation for ASP.NET MVC. You can also specify the minimum string length in this attribute, but the minimum value has no impact on the database schema.

也可使用屬性來指定數據驗證規則和相關的驗證錯誤信息。StringLength屬性設置了數據庫中該字段的最大長度,而且向ASP.NET MVC提供客戶端和服務器端驗證。你也可在該屬性中指定最小字符串長度,可是最小長度值對於數據庫結構來講是沒有必要的。

Suppose you want to ensure that users don’t enter more than 50 characters for a name. To add this limitation, add StringLength attributes to the LastName and FirstMidName properties, as shown in the following example:

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; }
    }
}

The StringLength attribute won’t prevent a user from entering white space for a name. You can use the RegularExpression attribute to apply restrictions to the input. For example the following code requires the first character to be upper case and the remaining characters to be alphabetical:

StringLength屬性不會阻止用戶在名字中輸入空格。你可使用RegulrExpression屬性來限制輸入。例如,下列代碼表示:第一個字符大寫其他字符是英文字母。

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

The MaxLength attribute provides functionality similar to the StringLength attribute but doesn’t provide client side validation.

MaxLength屬性提供的功能與StringLength屬性相似,但不能提供客戶端的驗證。

The database model has now changed in a way that requires a change in the database schema. You’ll use migrations to update the schema without losing any data that you may have added to the database by using the application UI.

如今數據模型變化了,進而須要更改數據庫結構。你將使用遷移功能更新數據庫結構,在這個過程當中並不會丟失那些可能經過應用界面已經增長到數據庫內的數據。

Save your changes and build the project. Then open the command window in the project folder and enter the following commands:

dotnet ef migrations add MaxLengthOnNames -c SchoolContext
dotnet ef database update -c SchoolContext

The migrations add command creates a file named <timeStamp>_MaxLengthOnNames.cs. This file contains code in the Up method that will update the database to match the current data model. The database update command ran that code.

migrations add 命令建立了名爲<時間戳>_MaxLengthOnNames.cs的文件。該文件中的Up方法包含的代碼將更新數據庫以匹配數據模型。database update命令將執行這些代碼。

The timestamp prefixed to the migrations file name is used by Entity Framework to order the migrations. You can create multiple migrations before running the update-database command, and then all of the migrations are applied in the order in which they were created.

EF利用遷移文件名稱重前置的時間戳將遷移進行排序。你可在運行update-database命令前建立多個遷移,接下來全部的遷移會按照建立順序執行。

Run the Create page, and enter either name longer than 50 characters. When you click Create, client side validation shows an error message.

The Column attribute

You can also use attributes to control how your classes and properties are mapped to the database. Suppose you had used the name FirstMidName for the first-name field because the field might also contain a middle name. But you want the database column to be named FirstName, because users who will be writing ad-hoc queries against the database are accustomed to that name. To make this mapping, you can use the Column attribute.

能夠使用屬性控制如何屬性映射數據庫假設已經使用名稱 FirstMidName 爲first-name字段,但字段可能包含一個中間想將數據庫命名爲FirstName由於數據庫臨時查詢用戶已經習慣這個名字若要使映射能夠使用屬性

The Column attribute specifies that when the database is created, the column of the Student table that maps to the FirstMidName property will be named FirstName. In other words, when your code refers to Student.FirstMidName, the data will come from or be updated in the FirstName column of the Student table. If you don’t specify column names, they are given the same name as the property name.

Column屬性說明了:當建立數據庫的時候,映射到FirstMidName屬性的Stduent表中的列將被命名爲FirstName。換句話說,當你的代碼指向Stduents.FirstMidName時,數據未來自或者更新到Student表的FirstName列。若是你不指定列名,列名將與屬性名相同。

In the Student.cs file, add a using statement for System.ComponentModel.DataAnnotations.Schema and add the column name attribute to the FirstMidName property, as shown in the following highlighted code:

在Student.cs文件中,增長一條using語句----System.ComponentModel.DataAnnotations.Schema,而且給FirstMidName屬性增長一個列名屬性,就像下面代碼中的高亮部分:

 
     
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; }
    }
}
 
     

 

Save your changes and build the project.

保存變動並生成項目。

The addition of the Column attribute changes the model backing the SchoolContext, so it won’t match the database.

增長的Column屬性改變了SchoolContext中的模型,因此這將與數據庫內的信息再也不相同。

Save your changes and build the project. Then open the command window in the project folder and enter the following commands to create another migration:

保存變動並生成項目。而後,在項目文件夾中打開命令行窗口,並鍵入下列命令來建立另一個遷移:

dotnet ef migrations add ColummFirstName -c SchoolContext
dotnet ef database update -c SchoolContext

In SQL Server Object Explorer, open the Student table designer by double-clicking the Student table.

Before you applied the first two migrations, the name columns were of type nvarchar(MAX). They are now nvarchar(50) and the column name has changed from FirstMidName to FirstName.

Note

If you try to compile before you finish creating all of the entity classes in the following sections, you might get compiler errors.

若是你在完成下面章節中建立全部實體類的工做前進行編譯,你將獲得編譯錯誤。

Final changes to the Student entity Student實體的最後變動

In Models/Student.cs, replace the code you added earlier with the following code. The changes are highlighted.

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; }
        [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; }
    }
}

 

The Table attribute

Table屬性

As you saw in the first tutorial, by default tables are named after the DbSet property name. The property name is for a collection, so it is typically plural (「Students」), but many developers and DBAs prefer to use the singular form (「Student」) for table names. This attribute specifies the name that EF will use for the table in the database that stores Student entities.

正如在第一篇教程中看到的,默認按照DbSet屬性名後面的名字給數據庫的表進行命名。然而,屬性名錶明瞭一個集合,因此通常地採用複數形式(「Students」),可是許多開發者和數據庫系統更喜歡用單數形式命名錶名。這個屬性指定了EF將在數據庫中存儲Stduent實體的表名。

The Required attribute

The Required attribute makes the name properties required fields. The Required attribute is not needed for non-nullable types such as value types (DateTime, int, double, float, etc.). Types that can’t be null are automatically treated as required fields.

Required屬性將字段屬性規定成是必須填寫的。對於非空類型來講Required屬性不是必須的(例如數值類型:DateTime, int, double, float等等)。非空類型被自動看成必須填寫的字段。

You could remove the Required attribute and replace it with a minimum length parameter for the StringLength attribute:

你能夠刪除Required屬性,而且將其替換爲限制最小長度的參數------StringLength屬性:

[Display(Name = "Last Name")]
[StringLength(50, MinimumLength=1)]
public string LastName { get; set; }

The Display attribute

The Display attribute specifies that the caption for the text boxes should be 「First Name」, 「Last Name」, 「Full Name」, and 「Enrollment Date」 instead of the property name in each instance (which has no space dividing the words).

Display屬性指定了文本框的標題爲「First Name」、「Last Name"、"Full Name"以及「Enrollment Date」,從而替代了原特性名(單詞中間沒有空格)。

The FullName calculated property FullName計算屬性

FullName is a calculated property that returns a value that’s created by concatenating two other properties. Therefore it has only a get accessor, and no FullName column will be generated in the database.

FullName是一個被計算出來的屬性,返回由其餘兩個屬性鏈接後造成的值。所以,它只有get運算符,而且在數據庫中不生成FullName列。

Create the Instructor Entity 新建講師實體

Create Models/Instructor.cs, replacing the template code with the following code:

新建Models/Instrctor.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> Courses { get; set; }
        public OfficeAssignment OfficeAssignment { get; set; }
    }
}

Notice that several properties are the same in the Student and Instructor entities. In the Implementing Inheritance tutorial later in this series, you’ll refactor this code to eliminate the redundancy.

請注意,有幾個屬性與Stduent和Instructor實體相同。在本系列教程後面的Implementing Inheritance (實施繼承)章節,你將重構這些代碼以消除這些冗餘。

You can put multiple attributes on one line, so you could also write the HireDate attributes as follows:

你能夠將多個屬性放在同一行中,因此也可寫爲:

[DataType(DataType.Date),Display(Name = "Hire Date"),DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]

The Courses and OfficeAssignment navigation properties

The Courses and OfficeAssignment properties are navigation properties.

CoursesOfficeAssignment屬性是導航屬性。

An instructor can teach any number of courses, so Courses is defined as a collection.

一個教師能夠教多門課程,因此Courses被定義成一個集合。

public ICollection<InstructorCourse> Courses { get; set; }

If a navigation property can hold multiple entities, its type must be a list in which entries can be added, deleted, and updated. You can specify ICollection<T> or a type such as List<T> or HashSet<T>. If you specify ICollection<T>, EF creates a HashSet<T> collection by default.

若是一個導航屬性可包含多個實體,其屬性必須是一個list,可實現實體的增長、刪除和更新。你可將其指定爲ICollection<T>,或者例如List<T>HashSet<T>等屬性。若是你指定了ICollection<T>,EF默認建立一個HashSet<T>集合。

The reason why these are InstructorCourse entities is explained below in the section about many-to-many relationships.

這就是爲何InstructorCourse實體在本節後續被解釋爲多對多關係的緣由。

Contoso University business rules state that an instructor can only have at most one office, so the OfficeAssignment property holds a single OfficeAssignment entity (which may be null if no office is assigned).

Contoso大學的商務條款指定:一個講師僅能有一個辦公室,因此OfficeAssignment屬性包含一個單數的OfficeAssignment實體(能夠爲null,若是沒有被指定辦公室的話)。

public virtual OfficeAssignment OfficeAssignment { get; set; }

Create the OfficeAssignment entity 建立辦公室分配實體

Create Models/OfficeAssignment.cs with the following code:

使用下列代碼新建Models/OfficeAssignment.cs

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

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

        public virtual Instructor Instructor { get; set; }
    }
}

 

The Key attribute Key屬性

There’s a one-to-zero-or-one relationship between the Instructor and the OfficeAssignment entities. An office assignment only exists in relation to the instructor it’s assigned to, and therefore its primary key is also its foreign key to the Instructor entity. But the Entity Framework can’t automatically recognize InstructorID as the primary key of this entity because its name doesn’t follow the ID or classnameID naming convention. Therefore, the Key attribute is used to identify it as the key:

InstructorOfficeAssignment實體間存在1對0或對1的關係。一個辦公室的安排僅與安排到辦公室的講師存在關係,所以它的主鍵以及外鍵是Instructor實體。實體框架由於名字並不遵循 ID classnameID命名約定,不能自動識別 InstructorID 實體主鍵所以Key屬性用於將其識別的主鍵。

[Key]
[ForeignKey("Instructor")]
public int InstructorID { get; set; }

You can also use the Key attribute if the entity does have its own primary key but you want to name the property something other than classnameID or ID.

若是實體確實具備主鍵,但你想命名成其餘的名稱,而不是classnameID或者ID,你也可以使用Key屬性。

By default EF treats the key as non-database-generated because the column is for an identifying relationship.

默認狀況下,EF按non-database-generated(不禁數據庫產生)來對待該主鍵 由於該列用於驗證關係。

The ForeignKey attribute ForeignKey屬性

When there is a one-to-zero-or-one relationship or a one-to-one relationship between two entities (such as between OfficeAssignment and Instructor), EF might not be able to work out which end of the relationship is the principal and which end is dependent. One-to-one relationships have a reference navigation property in each class to the other class. The ForeignKey attribute can be applied to the dependent class to establish the relationship.

例如OfficeAssignment和Instructor之間,當兩個實體間存在1-0或1關係,或者1-1關係時,EF 可能一端關係是主的一端是從的。在每一個類中都有一個指向另外一個類的導航屬性 ForeignKey 屬性能夠應用依賴創建關係

The Instructor navigation property Instructor導航屬性

The Instructor entity has a nullable OfficeAssignment navigation property (because an instructor might not have an office assignment), and the OfficeAssignment entity has a non-nullable Instructor navigation property (because an office assignment can’t exist without an instructor – InstructorID is non-nullable). When an Instructor entity has a related OfficeAssignment entity, each entity will have a reference to the other one in its navigation property.

Instructor實體有一個可爲null的OfficeAssignment導航屬性(由於一個講師能夠沒有辦公室的安排),OfficeAssignment實體有一個非空Instructor導航實體(由於一個辦公室的安排不存在沒有講師的狀況--InstructorID非空)。當講師實體有一個對應的OfficeAssignment實體,每一個實體都會引用另外一個實體看成導航屬性。

You could put a [Required] attribute on the Instructor navigation property to specify that there must be a related instructor, but you don’t have to do that because the InstructorID foreign key (which is also the key to this table) is non-nullable.

你能夠將[Required]屬性放在Instructor導航屬性的上面,以指定必須關聯一個講師,可是你不用必須這樣作,由於InstructorID外鍵(同時也是該表的主鍵)是非空的。

Modify the Course Entity 修改Course實體

In Models/Course.cs, replace the code you added earlier with the following code:

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> Assignments { get; set; }
    }
}

 

The course entity has a foreign key property DepartmentID which points to the related Department entity and it has a Department navigation property.

Course實體有一個DepartmentID外鍵屬性,指向相關的Department實體,同時也有一個Department導航屬性。

The Entity Framework doesn’t require you to add a foreign key property to your data model when you have a navigation property for a related entity. EF automatically creates foreign keys in the database wherever they are needed and creates shadow properties for them. But having the foreign key in the data model can make updates simpler and more efficient. For example, when you fetch a course entity to edit, the Department entity is null if you don’t load it, so when you update the course entity, you would have to first fetch the Department entity. When the foreign key property DepartmentID is included in the data model, you don’t need to fetch the Department entity before you update.

當有一個和某實體相關導航屬性時,EF不須要你給數據模型添加外鍵屬性。EF會在數據庫中自動建立外鍵,不管是否須要都會爲其建立shadow properties。可是,在數據模型中擁有外鍵可以使更新更加簡單和更富效率。例如,當你獲得一個course實體要編輯時,若是不加載Department,則Department實體是空的,因此當你更新course實體時,你將必須首先得到Deparment實體。當外鍵屬性DepartmentID包含在數據模型中時,你不須要在更新前得到Department實體。

The DatabaseGenerated attribute DatabaseGenerated屬性

The DatabaseGenerated attribute with the None parameter on the CourseID property specifies that primary key values are provided by the user rather than generated by the database.

CourseID屬性上面的DatabaseGenerated屬性帶有None參數,它指定了由用戶提供主鍵值,而不是由數據庫產生。

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

By default, the Entity Framework assumes that primary key values are generated by the database. That’s what you want in most scenarios. However, for Course entities, you’ll use a user-specified course number such as a 1000 series for one department, a 2000 series for another department, and so on.

默認狀況下,EF假定由數據庫生成主鍵。這在大多數場景中是這樣的。然而,對於Course實體,你會使用由用戶指定的課程編碼,例如1000系列由一個系使用,2000系列由另一個系使用等等。

The DatabaseGenerated attribute can also be used to generate default values, as in the case of database columns used to record the date a row was created or updated. For more information, see Generated Properties.

DatabaseGenerated屬性還可用於生成默認值,好比當數據庫的列用於記錄一個行被建立或更新的日期。更多的信息,請參看Generated Properties。

Foreign key and navigation properties 外鍵和導航屬性

The foreign key properties and navigation properties in the Course entity reflect the following relationships:

Course實體中的外鍵屬性和導航屬性反映瞭如下關係:

A course is assigned to one department, so there’s a DepartmentID foreign key and a Department navigation property for the reasons mentioned above.

課程被分配到一個系,由於上述緣由,因此有一個DepartmentID外鍵和一個Department導航屬性。

public int DepartmentID { get; set; }
public Department Department { get; set; }

A course can have any number of students enrolled in it, so the Enrollments navigation property is a collection:

public ICollection<Enrollment> Enrollments { get; set; }

A course may be taught by multiple instructors, so the Instructors navigation property is a collection:

public ICollection<Instructor> Instructors { get; set; }

 

Create the Department entity

Create Models/Department.cs with the following code:

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; }
    }
}

 

The Column attribute

Earlier you used the Column attribute to change column name mapping. In the code for the Department entity, the Column attribute is being used to change SQL data type mapping so that the column will be defined using the SQL Server money type in the database:

早前使用Column屬性改變了映射的列名。在Department實體代碼中,Column屬性被用於改變映射的SQL數據類型,因此在數據庫中該列將被定義成SQL Server的money類型。

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

Column mapping is generally not required, because the Entity Framework chooses the appropriate SQL Server data type based on the CLR type that you define for the property. The CLR decimal type maps to a SQL Server decimal type. But in this case you know that the column will be holding currency amounts, and the money data type is more appropriate for that.

一般狀況下列映射是不須要的,由於EF基於你設定屬性的CLR類型來選擇適當的SQL Server數據類型。CLR的decimal類型映射到SQL Server的decimal類型。可是在這種狀況下,知道該列信息包含貨幣金額貨幣類型較爲適合

Foreign key and navigation properties

The foreign key and navigation properties reflect the following relationships:

導航屬性反映如下關係:

A department may or may not have an administrator, and an administrator is always an instructor. Therefore the InstructorID property is included as the foreign key to the Instructor entity, and a question mark is added after the int type designation to mark the property as nullable. The navigation property is named Administrator but holds an Instructor entity:

一個部門可能可能沒有管理員管理員始終是講師所以 InstructorID 屬性將做爲講師實體的外鍵包含其中,在 int 類型後添加一個問號表明着屬性導航屬性命名爲Administrator持有講師實體
public int? InstructorID { get; set; }
public virtual Instructor Administrator { get; set; }

A department may have many courses, so there’s a Courses navigation property:

public ICollection<Course> Courses { get; set; }

Note

By convention, the Entity Framework enables cascade delete for non-nullable foreign keys and for many-to-many relationships. This can result in circular cascade delete rules, which will cause an exception when you try to add a migration. For example, if you didn’t define the Department.InstructorID property as nullable, EF would configure a cascade delete rule to delete the instructor when you delete the department, which is not what you want to have happen. If your business rules required the InstructorID property to be non-nullable, you would have to use the following fluent API statement to disable cascade delete on the relationship:

按照約定,EF對非空外鍵和多對多關係啓用級聯刪除。這可致使循環級聯刪除規則,當你嘗試增長遷移時這將致使異常。例如,若是你沒有將Department.InstrctiorID定義爲非空類型,當你刪除department時,EF將配置一個級聯刪除規則來刪除講師,這將不是你所想要的。若是你的商務規則須要InstructorID屬性做爲非空的,你將必須使用下列fluent API語句來關閉關係的級聯刪除。

modelBuilder.Entity<Department>()
  .HasOne(d => d.Administrator)
  .WithMany()
  .OnDelete(DeleteBehavior.Restrict)

Modify the Enrollment entity 修改註冊實體

In Models/Enrollment.cs, replace the code you added earlier with the following code:

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; }
    }
}

Foreign key and navigation properties 外鍵和導航屬性

The foreign key properties and navigation properties reflect the following relationships:

外鍵屬性和導航屬性反應瞭如下關係:

An enrollment record is for a single course, so there’s a CourseID foreign key property and a Course navigation property:

一條註冊記錄與單個課程對應,因此有一個CourseID外鍵屬性和一個Course導航屬性:

public int CourseID { get; set; }
public Course Course { get; set; }

An enrollment record is for a single student, so there’s a StudentID foreign key property and a Student navigation property:

一條註冊記錄與一個學生對應,因此有一個StduentID外鍵屬性和一個Student導航屬性:

public int StudentID { get; set; }
public Student Student { get; set; }

Many-to-Many Relationships 多對多關係

There’s a many-to-many relationship between the Student and Course entities, and the Enrollment entity functions as a many-to-many join table with payload in the database. 「With payload」 means that the Enrollment table contains additional data besides foreign keys for the joined tables (in this case, a primary key and a Grade property).

在Student和Course實體間有多對多的關係,Enrollment實體做爲多對多的橋樑,將數據庫中的表和有效載荷進行鏈接。"鏈接成有效載荷"意味着Enrollment表除了鏈接表的外鍵以外,還包含了一些附加的數據(在本例中,包含了主鍵和Grade屬性)。

The following illustration shows what these relationships look like in an entity diagram. (This diagram was generated using the Entity Framework Power Tools for EF 6.x; creating the diagram isn’t part of the tutorial, it’s just being used here as an illustration.)

下面的圖示形象地展示了實體圖中的關係。(該圖由Entity Framework Power Tools for EF 6.x。如何生成該圖不屬於本教程的範圍,僅用來圖示說明)

Each relationship line has a 1 at one end and an asterisk (*) at the other, indicating a one-to-many relationship.

每條表明關係的線一頭是1,另外一頭是星號(*),說明該關係是1對多的關係。

If the Enrollment table didn’t include grade information, it would only need to contain the two foreign keys CourseID and StudentID. In that case, it would be a many-to-many join table without payload (or a pure join table) in the database. The Instructor and Course entities have that kind of many-to-many relationship, and your next step is to create an entity class to function as a join table without payload.

若是Enrollment表不包含Grade的信息,則僅包含CourseID和StudentID兩個外鍵。在那種狀況下,這將成爲一個多對多的鏈接表,並不包含有效載荷(或者說是一個純鏈接表)。Instuctor和Course實體有這種多對多的關係,下一步將建立一個實體類,來定義一個沒有有效載荷的鏈接表。

The CourseAssignment entity 課程分配實體

A join table is required in the database for the Instructor-to-Courses many-to-many relationship, and CourseAssignment is the entity that represents that table.

數據庫中的鏈接表用於表達例如「講師-課程」之間的多對多關係,CourseAssignment就是表達這種表的實體。

Create Models/CourseAssignment.cs with the following code:

用下列代碼新建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; }
    }
}

 

Composite key 複合鍵

Since the foreign keys are not nullable and together uniquely identify each row of the table, there is no need for a separate primary key. The InstructorID and CourseID properties should function as a composite primary key. The only way to identify composite primary keys to EF is by using the fluent API (it can’t be done by using attributes). You’ll see how to configure the composite primary key in the next section.

由於外鍵是非空,而且每一行都驗證是惟一的,因此不須要單獨的主鍵。InstructorIDCourseID屬性將承擔複合鍵的做用。EF定義複合鍵的惟一方法是使用fluent API(使用屬性是作不到的)。下一章,你將看到如何配置複合主鍵。

The composite key ensures that while you can have multiple rows for one course, and multiple rows for one instructor, you can’t have multiple rows for the same instructor and course. The Enrollment join entity defines its own primary key, so duplicates of this sort are possible. To prevent such duplicates, you could add a unique index on the foreign key fields, or configure Enrollment with a primary composite key similar to CourseAssignment. For more information, see Indexes.

複合鍵保證了當一門課程有多行對應,而且一名講師有多行對應的狀況下,對於同一個講師和課程不會發生有多個行對應的狀況。Enrollment聯合實體定義了本身的主鍵,因此實現上述功能成爲可能。要避免這樣作,可在外鍵字段上增長一個惟一索引,或者給Enrollment配置一個相似於CoureAssignment的複合主鍵。更多的信息,請參看Indexs。

Join entity names 鏈接實體名稱

It’s common to name a join entity EntityName1EntityName2, which in this case would be CourseInstructor. However, we recommend that you choose a name that describes the relationship. Data models start out simple and grow, with no-payload joins frequently getting payloads later. If you start with a descriptive entity name, you won’t have to change the name later.

一般狀況下,將聯合實體命名爲EntityName1EntityName2,在這裏是CourseInstructor。可是,推薦你選擇表示關係的名稱。創建數據模型從簡單開始並逐漸擴展,在這個過程當中,無效的聯合逐漸變成有效的。若是你從描述明確的實體名稱開始,你將沒必要在後續修改這個名稱了。

Update the database context 更新數據庫上下文

Add the following highlighted code to the Data/SchoolContext.cs:

Data/SchoolContext.cs 中添加下列高亮代碼:

using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;

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

        public DbSet<Course> Courses { get; set; }
        public DbSet<Enrollment> Enrollments { get; set; }
        public DbSet<Student> Students { get; set; }
        public DbSet<Department> Departments { get; set; } public DbSet<Instructor> Instructors { get; set; } public DbSet<OfficeAssignment> OfficeAssignments { get; set; } public DbSet<CourseAssignment> CourseAssignments { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Course>().ToTable("Course");
            modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
            modelBuilder.Entity<Student>().ToTable("Student");
            modelBuilder.Entity<Department>().ToTable("Department"); modelBuilder.Entity<Instructor>().ToTable("Instructor"); modelBuilder.Entity<OfficeAssignment>().ToTable("OfficeAssignment"); modelBuilder.Entity<CourseAssignment>().ToTable("CourseAssignment"); modelBuilder.Entity<CourseAssignment>() .HasKey(c => new { c.CourseID, c.InstructorID });         }
    }
}

 

This code adds the new entities and configures the CourseAssignment entity’s composite primary key.

該代碼添加並配置了CourseAssignment實體的複合主鍵。

Fluent API alternative to attributes

The code in the OnModelCreating method of the DbContext class uses the fluent API to configure EF behavior. The API is called 「fluent」 because it’s often used by stringing a series of method calls together into a single statement, as in this example from the EF Core documentation:

DbContext類中的OnModelCreating方法使用了fluent API來配置EF的行爲。該API之因此被命名爲「fluent」是由於,它常常在一條語句中被連成一個方法串。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Property(b => b.Url)
        .IsRequired();
}

In this tutorial you’re using the fluent API only for database mapping that you can’t do with attributes. However, you can also use the fluent API to specify most of the formatting, validation, and mapping rules that you can do by using attributes. Some attributes such as MinimumLength can’t be applied with the fluent API. As mentioned previously, MinimumLength doesn’t change the schema, it only applies a client and server side validation rule.

在本教程中,你將僅使用fluent API處理不能用屬性進行設置的數據庫映射關係。然而,你也可以使用fluent API來指定格式、驗證以及映射關係等大部分可使用屬性來完成的工做。例如MinimumLength等一些屬性不能經過fluent API來設定。如同前面談到的,MinimumLength不能改變schema,其僅是進行客戶端和服務器端的驗證功能。

Some developers prefer to use the fluent API exclusively so that they can keep their entity classes 「clean.」 You can mix attributes and fluent API if you want, and there are a few customizations that can only be done by using fluent API, but in general the recommended practice is to choose one of these two approaches and use that consistently as much as possible. If you do use both, note that wherever there is a conflict, Fluent API overrides attributes.

一些開發者更願意使用fluent API,以便保持實體看起來更清晰。若是願意,你能夠混合使用屬性和fluent API,有一些定製化的工做只能經過使用fluent API來實現,但一般推薦的方法是儘量的擇其一而使用。若是二者都使用的話,要注意之間是否存在矛盾,若是存在矛盾,Fluent API要在屬性之上。

For more information about attributes vs. fluent API, see Methods of configuration.

Entity Diagram Showing Relationships

The following illustration shows the diagram that the Entity Framework Power Tools create for the completed School model.

Besides the one-to-many relationship lines (1 to *), you can see here the one-to-zero-or-one relationship line (1 to 0..1) between the Instructor and OfficeAssignment entities and the zero-or-one-to-many relationship line (0..1 to *) between the Instructor and Department entities.

Seed the Database with Test Data

Replace the code in the Data/DbInitializer.cs file with the following code in order to provide seed data for the new entities you’ve created.

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

namespace ContosoUniversity.Models
{
    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();
        }
    }
}

 

As you saw in the first tutorial, most of this code simply creates new entity objects and loads sample data into properties as required for testing. Notice how the many-to-many relationships are handled: the code creates relationships by creating entities in the Enrollments and CourseInstructor join entity sets.

Add a migration 添加遷移

Save your changes and build the project. Then open the command window in the project folder and enter the migrations add command (don’t do the update-database command yet):

保存變動,而且build項目。而後在項目文件夾中打開命令行窗口,鍵入migrations add命令(還不要使用update-database命令):

dotnet ef migrations add ComplexDataModel -c SchoolContext

You get a warning about possible data loss.

你獲得可能丟失數據的警告。

C:\ContosoUniversity\src\ContosoUniversity>dotnet ef migrations add ComplexDataModel -c SchoolContext
Project ContosoUniversity (.NETCoreApp,Version=v1.0) will be compiled because Input items removed from last build
Compiling ContosoUniversity for .NETCoreApp,Version=v1.0
Compilation succeeded.
    0 Warning(s)
    0 Error(s)
Time elapsed 00:00:02.9907258

An operation was scaffolded that may result in the loss of data. Please review the migration for accuracy.

Done.

To undo this action, use 'dotnet ef migrations remove'

 If you tried to run the database update command at this point (don’t do it yet), you would get the following error:

若是這時你嘗試運行database update命令(請先別這樣作),你將獲得下述錯誤:

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’.

Sometimes when you execute migrations with existing data, you need to insert stub data into the database to satisfy foreign key constraints. The generated code in the Up method adds a non-nullable DepartmentID foreign key to the Course table. If there are already rows in the Course table when the code runs, the AddColumn operation fails because SQL Server doesn’t know what value to put in the column that can’t be null. For this tutorial you’ll run the migration on a new database, but in a production application you’d have to make the migration handle existing data, so the following directions show an example of how to do that.

有些時候,當你使用已有數據進行遷移的時候,須要將存根數據插入數據庫來知足外鍵約束。在Up方法生成的代碼中,給Course表增長一個非空的DepartmentID外鍵。若是運行代碼時,在Course表中已經有這些行,AddColumn操做就會失敗,由於SQL Server不知道向這些非空的列中放置什麼值。在該教程中,你將在一個新數據庫中執行遷移,可是在生產應用中,你沒必要用遷移來處理已經存在的數據,因此下面的指導展現了一個例子,來講明如何這樣作。

To make this migration work with existing data you have to change the code to give the new column a default value, and create a stub department named 「Temp」 to act as the default department. As a result, existing Course rows will all be related to the 「Temp」 department after the Up method runs.

要在已經存在的數據上作遷移工做,你必須改變代碼,將一個默認值賦給新列,建立一個名爲「Temp」的存根department做爲department的默認值。在運行Up方法後,已有Course的行將所有與"Temp"department關聯起來。

Open the <timestamp>_ComplexDataModel.cs file. Comment out the line of code that adds the DepartmentID column to the Course table, and add before it the following code (the commented lines are also shown):

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);

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

In a production application, you would write code or scripts to add Department rows and relate Course rows to the new Department rows. You would then no longer need the 「Temp」 department or the default value on the Course.DepartmentID column.

Save your changes and build the project.

Change the connection string and update the database

You now have new code in the DbInitializer class that adds seed data for the new entities to an empty database. To make EF create a new empty database, change the name of the database in the connection string in appsettings.json to ContosoUniversity3 or some other name that you haven’t used on the computer you’re using.

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

Save your change to appsettings.json.

Note

As an alternative to changing the database name, you can delete the database. Use SQL Server Object Explorer (SSOX) or the database drop CLI command:

dotnet ef database drop -c SchoolContext

After you have changed the database name or deleted the database, run the database update command in the command window to execute the migrations.

dotnet ef database update -c SchoolContext

Run the app to cause the DbInitializer.Initialize method to run and populate the new database.

Open the database in SSOX as you did earlier, and expand the Tables node to see that all of the tables have been created. (If you still have SSOX open from the earlier time, click the Refresh button.)

Run the application to trigger the initializer code that seeds the database.

Right-click the CourseInstructors table and select View Data to verify that it has data in it.

 

You now have a more complex data model and corresponding database. In the following tutorial, you’ll learn more about how to access related data.

相關文章
相關標籤/搜索