Contoso University示例網站演示如何使用Entity Framework 5創建ASP.NET MVC 4應用程序。Entity Framework有三種處理數據的方式: Database First, Model First, and Code First. 本指南使用代碼優先。其它方式請查詢資料。示例程序是爲Contoso University建立一個網站。功能包括:學生管理、課程創建、教師分配。 本系列指南逐步講述如何實現這一網站程序。
本示例程序基於 ASP.NET MVC.如果使用 ASP.NET Web Forms model, 請查看 Model Binding and Web Forms系列指南和 ASP.NET Data Access Content Map.
如有問題,可在這些討論區提問: ASP.NET Entity Framework forum, the Entity Framework and LINQ to Entities forum, or StackOverflow.com.
目前在指南中使用的數據模型由三個實體組成。這一節你將添加更多實體和它們之間的關係,也將通過格式指明、驗證、數據庫映射規則對數據模型進行定製。你將學習兩種定製數據模型的方式:在實體類添加特性或在數據上下文類添加代碼。
完成之後的實體及其關係如下:
使用特性定製數據模型
本小節你將學習如何使用格式指明、驗證、數據映射規則等特性定製數據模型。隨後將完成所需要的數據模型。
DataType 特性
儘管只關心學生登記的日期,但現在所有顯示登記日期的網頁也顯示了時間。使用數據標註特性可修改一次代碼即完成對所有顯示格式的修復。
在 Models\Student.cs, 添加 using
語句引入 System.ComponentModel.DataAnnotations
命名空間,爲EnrollmentDate
屬性添加 DataType
和DisplayFormat
特性,代碼如下:
using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace ContosoUniversity.Models { public class Student { public int StudentID { 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 virtual ICollection<Enrollment> Enrollments { get; set; } } }
DataType 特性更明確的指明瞭數據的類型.
在此我們只關心日期,時間可以忽略.
DataType Enumeration提供了很多數據類型, 比如 Date, Time, PhoneNumber, Currency, EmailAddress等等.DataType
特性還能讓程序爲數據類型提供相應的功能.
如,會爲 DataType.EmailAddress添加mailto鏈接,
爲 DataType.Date 在瀏覽器提供支持HTML5的日期選擇.DataType 特性爲 HTML
5 瀏覽器提供可識別的數據.
DataType 不負責驗證.
DataType.Date
並沒有指明數據格式. 默認按服務器的 CultureInfo的格式.
DisplayFormat
用來明確指明數據格式:
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)] public DateTime EnrollmentDate { get; set; }
ApplyFormatInEditMode
指明是否在編輯狀態使用此格式. (有時可能不需要 — 例如, 對於貨幣值, 編輯時你可能不希望在輸入框出現貨幣符號.)
可單獨使用 DisplayFormat ,但同時最好使用 DataType .DataType
特性將數據的語義轉爲如何顯示,提供DisplayFormat不具備的功能
:
- 瀏覽器啓用 HTML5 功能(如顯示日期控件, 適當的貨幣符號, email 鏈接等.).
- 瀏覽器默認使用 locale定義的格式顯示數據.
- DataType 特性使得MVC選擇正確的呈現方式 (DisplayFormat 需要指明格式字符串). 更多信息請查看 ASP.NET MVC 2 Templates.
如果使用了 DataType
,還要使用 DisplayFormat
特性的原因是確保能在Chrome瀏覽器顯示正常.
更多信息請查看 this
StackOverflow thread.
運行程序,查看使用 Student
模型的視圖中關於日期是否不再顯示時間.
StringLength特性
你也可以通過特性對數據驗證規則和提示信息進行定製. 爲了限制LastName
和FirstMidName
屬性的長度不要超過50個字符,可使用StringLength特性:
using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace ContosoUniversity.Models { public class Student { public int StudentID { 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 virtual ICollection<Enrollment> Enrollments { get; set; } } }
StringLength 並不阻止輸入空字符串。可使用RegularExpression 特性限制輸入格式. 如下面的特性值限制輸入的首字符必須是大寫,隨後輸入的都是字母:
[RegularExpression(@"^[A-Z]+[a-zA-Z''-'\s]*$")]
MaxLength 特性和 StringLength 特性相似,但前者不提供客戶端驗證功能.
運行程序,點擊Students . 將發生如下錯誤:
The model backing the 'SchoolContext' context has changed since the database was created. Consider using Code First Migrations to update the database (http://go.microsoft.com/fwlink/?LinkId=238269).
數據模型發生變化要求數據庫格式相應變化, Entity Framework能檢測到這一問題. 可使用遷移命令在數據不丟失的同時升級數據庫.如果修改了通過 Seed
錄入的數據,
將恢復到初始狀態。這是因爲Seed 方法中調用了 AddOrUpdate
在Package Manager Console (PMC), 輸入如下命令:
add-migration MaxLengthOnNames
update-database
add-migration MaxLengthOnNames
創建了名爲 <timeStamp>_MaxLengthOnNames.cs的文件.
其中包含了更新數據庫以匹配當前數據模型的代碼。文件名之前的時間戳被Entity Framework 用來識別遷移的順序. 在創建了多次遷移之後,如果刪除了數據庫或者使用遷移部署項目,所有的遷移將按照創建順序執行。
運行創建頁面,如果姓名輸入字符長度超出50,提示信息立即就顯示了.
Column 屬性
可使用特性控制類和屬性如何映射到數據庫。比如使用FirstMidName
作爲名字,但數據庫列名依然希望是FirstName,可使用Column
特性。Y
Column
特性指明數據庫創建時,Student
表映射FirstMidName屬性的列名是FirstName。換句話說,如果你的代碼引用Student.FirstMidName,數據將來自Student
表的FirstName
列。如果不指明列名,列名和屬性名將保持一致.
添加using引入 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 StudentID { 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 virtual ICollection<Enrollment> Enrollments { get; set; } } }
Column attribute 改變了 SchoolContext背後的模型, 和數據庫不再匹配. 輸入以下命令執行遷移:
add-migration ColumnFirstName
update-database
在 Server Explorer, 查看Student 表.
可使用 Fluent API完成數據庫的映射, 隨後將提到這一功能.
Note在完成所有模型的代碼之前編譯程序可能導致編譯失敗.
創建Instructor Entity
創建 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 InstructorID { 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; } public string FullName { get { return LastName + ", " + FirstMidName; } } public virtual ICollection<Course> Courses { get; set; } public virtual OfficeAssignment OfficeAssignment { get; set; } } }
在 Student
和Instructor
實體中有一些屬性相同.你將在隨後的 Implementing
Inheritance 部分, 使用繼承來消除冗餘.
Required 和Display 特性
LastName
屬性的Required特性指明此屬性是不能爲空的,Display屬性指明瞭此屬性顯示名稱.
[Required] [Display(Name="Last Name")] [StringLength(50)] public string LastName { get; set; }
Required
attribute 對 DateTime, int, double, 和float這樣的值類型來說是不需要的. 值類型本身就不允許賦 null 值, 因此它們默認就是required. 爲 StringLength
特性設定一個最短長度可以不必使用required特性了:
[Display(Name = "Last Name")] [StringLength(50, MinimumLength=1)] public string LastName { get; set; }
可在同一行寫多個特性,因此 instructor 類也可以這樣寫:
public class Instructor { public int InstructorID { get; set; } [Display(Name = "Last Name"),StringLength(50, MinimumLength=1)] public string LastName { get; set; } [Column("FirstName"),Display(Name = "First Name"),StringLength(50, MinimumLength=1)] public string FirstMidName { get; set; } [DataType(DataType.Date),Display(Name = "Hire Date"),DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)] public DateTime HireDate { get; set; } public string FullName { get { return LastName + ", " + FirstMidName; } } public virtual ICollection<Course> Courses { get; set; } public virtual OfficeAssignment OfficeAssignment { get; set; } }
FullName 計算屬性
FullName是計算屬性,其返回值來自其它兩個屬性的計算
. 因此它只有 get
訪問器,
數據庫中不會有 FullName
列.
public string FullName { get { return LastName + ", " + FirstMidName; } }
Courses 和 OfficeAssignment 導航屬性
之前曾經講過導航屬性,通常使用 virtual 以便利用
Entity Framework 的 lazy
loading. 另外, 如果導航屬性包含多個對象,它的類型必須是實現了 ICollection<T> 接口.
(例如 IList<T> 可以,但 IEnumerable<T> 不行,因爲IEnumerable<T>
沒有實現 Add.
一個老師可以上多門課,因此 Courses
被定義爲 Course
實體的集合.
我們的業務規則要求老師最多隻能有一個辦公室, 因此 OfficeAssignment
定義爲OfficeAssignment
實體的單個變量(如果沒有分配辦公室,值可能是 null
).
public virtual ICollection<Course> Courses { get; set; } public virtual OfficeAssignment OfficeAssignment { get; set; }
創建 OfficeAssignment Entity
創建 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; } } }
編譯,你需要保證編譯通過.
Key 特性
在 Instructor
和OfficeAssignment
實體之間是1對0或1的關係.
因此office assignment的主鍵同時是 Instructor
的外鍵. 但 Entity Framework 不能自動識別 InstructorID
作爲OfficeAssignment
的主鍵,因爲其命名沒有遵循默認主鍵是 ID
or classnameID
命名約定.
因此 Key
特性用來指明主鍵:
[Key] [ForeignKey("Instructor")] public int InstructorID { get; set; }
如果主鍵的名字不想使用 classnameID
或ID,你也可以使用Key特性指明主鍵
.
ForeignKey 特性
對於兩個實體之間1對0或1的關係或者1對1的關係(如OfficeAssignment 和Instructor),EF無法識別哪一方是主方,哪一方是依賴方。1對1關係在每個類裏都有導航屬性指向對方。ForeignKey特性應用到依賴方的類,建立明確的關係,如果忽略了,你將在遷移時出現如下異常:
Unable to determine the principal end of an association between the types 'ContosoUniversity.Models.OfficeAssignment' and 'ContosoUniversity.Models.Instructor'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.
隨後我們將講解如何使用fluent API解決這一問題.
Instructor 導航屬性
Instructor
實體有一個 nullable OfficeAssignment
導航屬性
(老師可能麼有分配辦公室), OfficeAssignment
有一個 non-nullable Instructor
導航屬性
(一個分配不可能沒有老師-- InstructorID
是 non-nullable). 當一個Instructor
實體和 OfficeAssignment
實體有關係時,
二者相互有指向對方的導航屬性.
你可以在Instructor 導航屬性增加 [Required]
特性指明必須有一個
instructor, 但 InstructorID 外鍵 (同時也是此表的主鍵) 已經是 non-nullable,因此不必添加.
修改 Course Entity
在 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; } [Display(Name = "Department")] public int DepartmentID { get; set; } public virtual Department Department { get; set; } public virtual ICollection<Enrollment> Enrollments { get; set; } public virtual ICollection<Instructor> Instructors { get; set; } } }
course實體的DepartmentID
外鍵指向和它相關的Department實體,同時還有一個Department導航屬性
。在EF中,當有導航屬性後就不需要外鍵屬性了。EF會在數據庫自動創建需要的外鍵。但是在數據模型中使用外鍵使得更新更簡單高效。例如,當你修改一個course實體,如果沒有加載Department
,則其值爲null,當更新course實體時,你需要首先找到Department
實體。如果數據模型包含了DepartmentID
外鍵,就不需要這麼做了。
DatabaseGenerated 特性
DatabaseGenerated特性的 None 指明 CourseID
屬性的值雖然是主鍵,但由用戶賦值而非數據庫自動生成.
[DatabaseGenerated(DatabaseGeneratedOption.None)] [Display(Name = "Number")] public int CourseID { get; set; }
Entity Framework 默認主鍵值由數據庫自動生成.有時不希望這樣時,就可以使用 DatabaseGenerated
.
外鍵和導航屬性
Course
的外鍵和導航屬性反映瞭如下關係:
-
course隸屬一個 department, 因此有
DepartmentID
外鍵和Department導航屬性
.public int DepartmentID { get; set; } public virtual Department Department { get; set; }
-
A course 有多個 student註冊登記,因此
Enrollments
導航屬性是一個集合:public virtual ICollection<Enrollment> Enrollments { get; set; }
-
A course 可能由 instructors講授, 因此
Instructors
導航屬性是集合:public virtual ICollection<Instructor> Instructors { get; set; }
創建Department Entity
創建 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)] public DateTime StartDate { get; set; } [Display(Name = "Administrator")] public int? InstructorID { get; set; } public virtual Instructor Administrator { get; set; } public virtual ICollection<Course> Courses { get; set; } } }
Column 特性
之前使用 Column
attribu修改映射的列名.在 Department
實體,Column
特性用來修改
SQL數據類型以便數據庫中此列使用 money類型:
[Column(TypeName="money")] public decimal Budget { get; set; }
通常Entity Framework會爲數據庫的列選擇適當的類型。這裏只是進一步明確了數據庫要使用的類型。
外鍵和導航屬性
外鍵和導航屬性如下:
-
一個部門可能有一個管理員,管理員也是老師,因此這裏有如下外鍵和屬性:
public int? InstructorID { get; set; } public virtual Instructor Administrator { get; set; }
-
A department 可以有多門 courses, 因此有一個
Courses
導航屬性public virtual ICollection<Course> Courses { get; set; }
注意:EF在多對多關係中刪除不爲空的外鍵時採用瀑布模式。這可能導致循環瀑布刪除,從而引發異常:“引用關係導致循環引用”。如果業務規則要求InstructorID
不爲空,可使用如下代碼禁用瀑布刪除:
modelBuilder.Entity().HasRequired(d => d.Administrator).WithMany().WillCascadeOnDelete(false);
修改 Student Entity
在 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 StudentID { get; set; } [StringLength(50, MinimumLength = 1)] public string LastName { get; set; } [StringLength(50, MinimumLength = 1, 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)] [Display(Name = "Enrollment Date")] public DateTime EnrollmentDate { get; set; } public string FullName { get { return LastName + ", " + FirstMidName; } } public virtual ICollection<Enrollment> Enrollments { get; set; } } }
Enrollment Entity
Models\Enrollment.cs, 代碼如下using System.ComponentModel.DataAnnotations; 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 virtual Course Course { get; set; } public virtual Student Student { get; set; } } }
外鍵和導航屬性
關係如下:
-
一條登記信息對應一門課程,因此有
CourseID
屬性和Course導航屬性
:public int CourseID { get; set; } public virtual Course Course { get; set; }
-
一條登記信息對應一個學生,因此有
StudentID
外鍵屬性Student
導航屬性:public int StudentID { get; set; } public virtual Student Student { get; set; }
多對多關係
在 Student
和Course
實體之間是多對多關係,
Enrollment實體的作用是作爲二者之間的連接表
. 也就是說 Enrollment
表包含額外的外鍵之外的信息(本例中,主鍵和Grade屬性).
下圖顯示了這些實體之間的關係. (圖是使用 Entity Framework Power Tools生成的; )
由兩個一對多的關係組成.
如果 Enrollment
不需要保存Grade信息,只需要CourseID
and StudentID
.
就成了單純的連接表不包含額外信息, 那Enrollment
實體就無需存在了.
Instructor
和Course
直接建立多對多的關係:
但數據庫中還是需要連接表的:
Entity Framework自動創建了 CourseInstructor
表, 在讀取和更新時你會間接用到它。
實體圖顯示了實體之間的關係
Entity Framework Power Tools創建的完整的 School 模型圖.
實體間有一對一關係、一對一或零關係,一對多關係和多對多關係.
向 Database Context添加代碼實現模型定製
下一步向 SchoolContext
添加新的實體並使用fluent
API定製映射關係.
在本指南中你將使用fluent API完成無法通過特性實現的數據映射。但你也可使用它指明格式、驗證等通過特性完成的工作,有些是無法完成的,如MinimumLength
,如前面說的MinimumLength
不改變數據庫的結構,只是客戶端或服務端的驗證。一些開發人員優先使用fluentAPI以保持他們的代碼“乾淨”。
using ContosoUniversity.Models; using System.Data.Entity; using System.Data.Entity.ModelConfiguration.Conventions; namespace ContosoUniversity.DAL { public class SchoolContext : DbContext { public DbSet<Course> Courses { get; set; } public DbSet<Department> Departments { get; set; } public DbSet<Enrollment> Enrollments { get; set; } public DbSet<Instructor> Instructors { get; set; } public DbSet<Student> Students { get; set; } public DbSet<OfficeAssignment> OfficeAssignments { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Conventions.Remove<PluralizingTableNameConvention>(); modelBuilder.Entity<Course>() .HasMany(c => c.Instructors).WithMany(i => i.Courses) .Map(t => t.MapLeftKey("CourseID") .MapRightKey("InstructorID") .ToTable("CourseInstructor")); } } }
OnModelCreating 方法中的新代碼建立了多對多的連接表:
· 在 Instructor 和 Course 實體之間,代碼指明鏈接表和列的名字.代碼優先可使得無需代碼即可完成多對多的關係,但如果不寫代碼可能會使用 InstructorInstructorID 作爲InstructorID 列的名字.
-
modelBuilder.Entity<Course>() .HasMany(c => c.Instructors).WithMany(i => i.Courses) .Map(t => t.MapLeftKey("CourseID") .MapRightKey("InstructorID") .ToTable("CourseInstructor"));
下面的代碼是使用fluent API完成 Instructor 和 OfficeAssignment 實體之間的關係::
modelBuilder.Entity<Instructor>() .HasOptional(p => p.OfficeAssignment).WithRequired(p => p.Instructor);
更多信息請查看 Fluent API .
使用測試數據向數據庫添加數據
替換 Migrations\Configuration.cs 代碼,添加新實體的測試數據
namespace ContosoUniversity.Migrations { using System; using System.Collections.Generic; using System.Data.Entity; using System.Data.Entity.Migrations; using System.Linq; using ContosoUniversity.Models; using ContosoUniversity.DAL; internal sealed class Configuration : DbMigrationsConfiguration<SchoolContext> { public Configuration() { AutomaticMigrationsEnabled = false; } protected override void Seed(SchoolContext context) { var students = new List<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") } }; students.ForEach(s => context.Students.AddOrUpdate(p => p.LastName, s)); context.SaveChanges(); var instructors = new List<Instructor> { new Instructor { FirstMidName = "Kim", LastName = "Abercrombie", HireDate = DateTime.Parse("1995-03-11") }, new Instructor { FirstMidName = "Fadi", LastName = "Fakhouri", HireDate = DateTime.Parse("2002-07-06") }, new Instructor { FirstMidName = "Roger", LastName = "Harui", HireDate = DateTime.Parse("1998-07-01") }, new Instructor { FirstMidName = "Candace", LastName = "Kapoor", HireDate = DateTime.Parse("2001-01-15") }, new Instructor { FirstMidName = "Roger", LastName = "Zheng", HireDate = DateTime.Parse("2004-02-12") } }; instructors.ForEach(s => context.Instructors.AddOrUpdate(p => p.LastName, s)); context.SaveChanges(); var departments = new List<Department> { new Department { Name = "English", Budget = 350000, StartDate = DateTime.Parse("2007-09-01"), InstructorID = instructors.Single( i => i.LastName == "Abercrombie").InstructorID }, new Department { Name = "Mathematics", Budget = 100000, StartDate = DateTime.Parse("2007-09-01"), InstructorID = instructors.Single( i => i.LastName == "Fakhouri").InstructorID }, new Department { Name = "Engineering", Budget = 350000, StartDate = DateTime.Parse("2007-09-01"), InstructorID = instructors.Single( i => i.LastName == "Harui").InstructorID }, new Department { Name = "Economics", Budget = 100000, StartDate = DateTime.Parse("2007-09-01"), InstructorID = instructors.Single( i => i.LastName == "Kapoor").InstructorID } }; departments.ForEach(s => context.Departments.AddOrUpdate(p => p.Name, s)); context.SaveChanges(); var courses = new List<Course> { new Course {CourseID = 1050, Title = "Chemistry", Credits = 3, DepartmentID = departments.Single( s => s.Name == "Engineering").DepartmentID, Instructors = new List<Instructor>() }, new Course {CourseID = 4022, Title = "Microeconomics", Credits = 3, DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID, Instructors = new List<Instructor>() }, new Course {CourseID = 4041, Title = "Macroeconomics", Credits = 3, DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID, Instructors = new List<Instructor>() }, new Course {CourseID = 1045, Title = "Calculus", Credits = 4, DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID, Instructors = new List<Instructor>() }, new Course {CourseID = 3141, Title = "Trigonometry", Credits = 4, DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID, Instructors = new List<Instructor>() }, new Course {CourseID = 2021, Title = "Composition", Credits = 3, DepartmentID = departments.Single( s => s.Name == "English").DepartmentID, Instructors = new List<Instructor>() }, new Course {CourseID = 2042, Title = "Literature", Credits = 4, DepartmentID = departments.Single( s => s.Name == "English").DepartmentID, Instructors = new List<Instructor>() }, }; courses.ForEach(s => context.Courses.AddOrUpdate(p => p.CourseID, s)); context.SaveChanges(); var officeAssignments = new List<OfficeAssignment> { new OfficeAssignment { InstructorID = instructors.Single( i => i.LastName == "Fakhouri").InstructorID, Location = "Smith 17" }, new OfficeAssignment { InstructorID = instructors.Single( i => i.LastName == "Harui").InstructorID, Location = "Gowan 27" }, new OfficeAssignment { InstructorID = instructors.Single( i => i.LastName == "Kapoor").InstructorID, Location = "Thompson 304" }, }; officeAssignments.ForEach(s => context.OfficeAssignments.AddOrUpdate(p => p.Location, s)); context.SaveChanges(); AddOrUpdateInstructor(context, "Chemistry", "Kapoor"); AddOrUpdateInstructor(context, "Chemistry", "Harui"); AddOrUpdateInstructor(context, "Microeconomics", "Zheng"); AddOrUpdateInstructor(context, "Macroeconomics", "Zheng"); AddOrUpdateInstructor(context, "Calculus", "Fakhouri"); AddOrUpdateInstructor(context, "Trigonometry", "Harui"); AddOrUpdateInstructor(context, "Composition", "Abercrombie"); AddOrUpdateInstructor(context, "Literature", "Abercrombie"); context.SaveChanges(); var enrollments = new List<Enrollment> { new Enrollment { StudentID = students.Single(s => s.LastName == "Alexander").StudentID, CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID, Grade = Grade.A }, new Enrollment { StudentID = students.Single(s => s.LastName == "Alexander").StudentID, CourseID = courses.Single(c => c.Title == "Microeconomics" ).CourseID, Grade = Grade.C }, new Enrollment { StudentID = students.Single(s => s.LastName == "Alexander").StudentID, CourseID = courses.Single(c => c.Title == "Macroeconomics" ).CourseID, Grade = Grade.B }, new Enrollment { StudentID = students.Single(s => s.LastName == "Alonso").StudentID, CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID, Grade = Grade.B }, new Enrollment { StudentID = students.Single(s => s.LastName == "Alonso").StudentID, CourseID = courses.Single(c => c.Title == "Trigonometry" ).CourseID, Grade = Grade.B }, new Enrollment { StudentID = students.Single(s => s.LastName == "Alonso").StudentID, CourseID = courses.Single(c => c.Title == "Composition" ).CourseID, Grade = Grade.B }, new Enrollment { StudentID = students.Single(s => s.LastName == "Anand").StudentID, CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID }, new Enrollment { StudentID = students.Single(s => s.LastName == "Anand").StudentID, CourseID = courses.Single(c => c.Title == "Microeconomics").CourseID, Grade = Grade.B }, new Enrollment { StudentID = students.Single(s => s.LastName == "Barzdukas").StudentID, CourseID = courses.Single(c => c.Title == "Chemistry").CourseID, Grade = Grade.B }, new Enrollment { StudentID = students.Single(s => s.LastName == "Li").StudentID, CourseID = courses.Single(c => c.Title == "Composition").CourseID, Grade = Grade.B }, new Enrollment { StudentID = students.Single(s => s.LastName == "Justice").StudentID, CourseID = courses.Single(c => c.Title == "Literature").CourseID, Grade = Grade.B } }; foreach (Enrollment e in enrollments) { var enrollmentInDataBase = context.Enrollments.Where( s => s.Student.StudentID == e.StudentID && s.Course.CourseID == e.CourseID).SingleOrDefault(); if (enrollmentInDataBase == null) { context.Enrollments.Add(e); } } context.SaveChanges(); } void AddOrUpdateInstructor(SchoolContext context, string courseTitle, string instructorName) { var crs = context.Courses.SingleOrDefault(c => c.Title == courseTitle); var inst = crs.Instructors.SingleOrDefault(i => i.LastName == instructorName); if (inst == null) crs.Instructors.Add(context.Instructors.Single(i => i.LastName == instructorName)); } } }
和之前一樣,只是添加了測試用的數據. 不過要注意 Course
實體, 它和 Instructor
實體存在多對多關係,處理的方法是:
var courses = new List<Course> { new Course {CourseID = 1050, Title = "Chemistry", Credits = 3, Department = departments.Single( s => s.Name == "Engineering"), Instructors = new List<Instructor>() }, ... }; courses.ForEach(s => context.Courses.AddOrUpdate(p => p.CourseID, s)); context.SaveChanges();
當創建Course
對象,你需要使用代碼Instructors
= new List<Instructor>()初始化Instructiors導航屬性爲一個空集合,這樣可以使用Instructors.Add方法添加和 Course相關的
Instructor。如果不初始化空列表則無法使用Add方法向其中添加,你也可以在構造函數中初始化列表。
添加遷移,更新數據庫
使用如下命令添加遷移:
PM> add-Migration Chap4
此時如果更新數據庫,將發生如下錯誤:
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'.
編輯<timestamp>_Chap4.cs 文件, 修改代碼如下 (添加一條SQL語句,修改 AddColumn
語句):
CreateTable( "dbo.CourseInstructor", c => new { CourseID = c.Int(nullable: false), InstructorID = c.Int(nullable: false), }) .PrimaryKey(t => new { t.CourseID, t.InstructorID }) .ForeignKey("dbo.Course", t => t.CourseID, cascadeDelete: true) .ForeignKey("dbo.Instructor", t => t.InstructorID, cascadeDelete: true) .Index(t => t.CourseID) .Index(t => t.InstructorID); // Create a department for course to point to. Sql("INSERT INTO dbo.Department (Name, Budget, StartDate) VALUES ('Temp', 0.00, GETDATE())"); // default value for FK points to department created above. AddColumn("dbo.Course", "DepartmentID", c => c.Int(nullable: false, defaultValue: 1)); //AddColumn("dbo.Course", "DepartmentID", c => c.Int(nullable: false)); AlterColumn("dbo.Course", "Title", c => c.String(maxLength: 50)); AddForeignKey("dbo.Course", "DepartmentID", "dbo.Department", "DepartmentID", cascadeDelete: true); CreateIndex("dbo.Course", "DepartmentID"); } public override void Down() {
有時在執行已存在數據的遷移時,需要添加根數據以滿足外鍵約束,如上所做的一樣。生成的代碼爲Course
表添加了一個非空外鍵DepartmentID。如果Course
表已經有數據,AddColumn
操作將失敗,因爲SQL
Server不知道該爲不能爲空的列賦什麼值。改變代碼給它一個默認值,創建名爲Temp的一個根department來作爲默認department。這樣的話,如果已經存在數據,則和“Temp”相關。
Seed
方法運行時,將把“Temp”數據插入到Department
表,將已經存在的Course
表中的記錄和它相關聯。
修改 <timestamp>_Chap4.cs 文件後, 運行 update-database
命令執行遷移.注意:
在遷移數據或修改結構時可能發生錯誤。如果你解決不了錯誤,可以修改配置文件中的連接字符串或者刪除數據庫以便重新生成。最簡單的辦法就是修改連接字符串中的數據庫名字,比如:
<add name="SchoolContext" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=CU_Test; Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\CU_Test.mdf" providerName="System.Data.SqlClient" />如何刪除數據庫請查看 How to Drop a Database from Visual Studio 2012.
這是最新的數據庫結構:
你並沒有爲 CourseInstructor
表創建模型類.如之前所提到的,它只是 Instructor
和Course
實體之間的連接表.
右擊CourseInstructor
表選擇查看數據,查看錶中是否有你加入 Course.Instructors導航屬性的Instructor相關信息
.
總結
你已經創建了更加複雜的數據模型和相應的數據庫。隨後你將學習更多訪問關聯數據的方法。
Entity Framework 相關資源請查看 ASP.NET Data Access Content Ma