MVC5 Entity Framework学习之创建复杂的数据模型
Contoso University示例程序演示了如何使用Entity Framework 6 Code First 和 Visual Studio 2013创建ASP.NETMVC 5应用程序。
在上一篇文章中你已经创建了由三个实体组成的简单的数据模型。在本文章中你将会添加更多的实体和关系,并且通过指定格式、验证和数据库映射规则来自定义数据模型。这里介绍两种自定义数据模型的方法:向实体类中添加属性和向数据库上下文类中添加代码。
下面是完成后的数据模型类图
1.使用属性来自定义数据模型
在本节中你将学习如何通过使用指定的格式、验证和数据库映射规则属性来自定义数据模型,在接下来的章节中,你将通过向你已经创建的类或者为模型中剩余的实体类型创建的新类中添加属性来创建完整的School数据模型。
DataType属性
对于学生入学日期,所有的页面都是显示时间和日期,即使你只在意该字段中的日期部分。通过使用数据批注属性,你可以只添加一行代码就可以在每一个视图中使用特定的格式显示数据。要做到这一点,你需要向Student 类中的EnrollmentDate属性添加一个属性。
打开Models\Student.cs,添加System.ComponentModel.DataAnnotations命名空间,为EnrollmentDate属性添加DateType和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 virtual ICollection<Enrollment> Enrollments { get; set; }
- }
- }
DataType属性指明了一个比数据库内部类型更加具体的数据类型,在这种情况下,我们要显示的仅仅是日期,而不是日期和时间。DataType Enumeration提供了多种数据类型,比如Date, Time, PhoneNumber, Currency, EmailAddress等。DataType属性同样可以让应用程序来自动提供特定类型,例如DataType.EmailAddress可以创建mailto:超链接,DataType.Date属性可以在支持HTML5的浏览器中创建一个日期选择器。DataType属性可以生成Html5浏览器能够识别的HTML 5 data-(读数据破折号)属性,但DataType特性并不提供任何验证。
DataType.Date并没有指明日期的显示格式,默认情况下是根据服务器的CultureInfo来显示数据字段的格式。
DisplayFormat属性用来显示的指明要显示的日期格式
- [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
你可以只使用一个DisplayFormat属性,当通常比较好的做法是同时也使用DataType属性。DataType属性传达的是数据本身的语义而不是如何将它呈现在屏幕上,并且它提供了使用DisplayFormat时所不具备的优势:
- 浏览器可以启用HTML5功能(比如显示日历控件,本地化的货币符号,电子邮件链接,客户端输入验证等)
- 默认情况下,浏览器将使用基于本地区域设置的正确格式来呈现数据
- DataType属性可以让MVC自动选择正确的字段模板来呈现数据(DisplayFormat使用字符串模板)
如果日期字段使用了DataType属性,你还必须指定DisplayFormat属性以确保在Chrome浏览器中能正确呈现该字段。
运行项目,打卡Students选项卡,可以注意到Enrollment Date列不再显示时间部分,同样在任何使用Student 模型的视图中都会如此。
StringLength属性
你还可以使用属性来指定数据验证规则和验证错误信息,StringLength属性可以设定数据库中字段的最大长度并为ASP.Net MVC提供客户端和服务器端验证,当然你也可以使用该属性来设定字段的最小长度,但设定最小值并不影响数据库架构。
假设对于名字字段你想要确保用户输入不能超过50个字符,你需要为LastName和FirstMidName属性添加StringLength属性来限制用户输入,如下所示
- 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 virtual ICollection<Enrollment> Enrollments { get; set; }
- }
- }
- [RegularExpression(@"^[A-Z]+[a-zA-Z''-'\s]*$")]
运行项目并点击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方法时会将其更改回原始状态(AddOrUpdate相当于数据库中的"upsert"操作)。
在 Package Manager Console (PMC)中输入下列命令:
- add-migration MaxLengthOnNames
- update-database
Entity Framework在迁移文件名中使用时间戳以便按顺序执行迁移程序。在运行update-database命令之前,你可以创建多个迁移,所有的迁移会按照它们创建的顺序来执行。
运行项目,打开Create页面,在LastName文本框中输入超过50个字符,点击Create,客户端会验证此字段并显示错误消息:
Column 属性
你还可以通过使用属性来控制如何将类和属性映射到数据库。假设你使用FirstMidName作为名称字段,因为该字段中还可能包含一个中间名。但是你希望将数据库列命名为FirstName,因为那些写数据库查询语句的用户已经习惯与使用该列名。要完成此映射,你需要使用Column 属性。
Column属性指定当数据库被创建时,Student表中与FirstMidName属性映射的列将被命名为FirstName。换句话说,当你在代码中使用Student.FirstMidName时,该值会从Student表中的FirstName列查询到。如果你没有指定列的名称,该列会使用属性名作为列名。
打开 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 virtual ICollection<Enrollment> Enrollments { get; set; }
- }
- }
- add-migration ColumnFirstName
- update-database
下面的截图中可以看到在没有应用前两次迁移时原来的列名,现在FirstMidName已经被命名为FirstName,这两列的数据最大长度都已经有MAX更改为50个字符
你也可以使用Fluent API来实现数据库映射。
注意:如果你在完成所有实体类之前试图编译该应用程序,你会得到编译错误。
2.完成对Student实体的更改
打开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 virtual ICollection<Enrollment> Enrollments { get; set; }
- }
- }
Required 属性
Required属性设置名称属性为必填字段,值类型的字段是不需要Required属性的,例如DateTime, int, double, 和float。值类型不能被赋值为null值,所以它们本身就被视为必填字段。你也可以使用带有最小长度参数的StringLengthsh属性来替换Required属性。
- [Display(Name = "Last Name")]
- [StringLength(50, MinimumLength=1)]
- public string LastName { get; set; }
Display 属性
Display属性指定文本框的标题应该是"First Name", "Last Name", "Full Name"和"Enrollment Date",而不是每一个实例中属性本身的名字(那些中间没有空格的单词)。
FullName计算属性
FullName是一个计算属性,它返回一个由其它两个属性相连接后的值,因此它只有get访问方法,数据库也不会生成对应的FullName列。
3.创建Instructor实体
新建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 virtual ICollection<Course> Courses { get; set; }
- public virtual OfficeAssignment OfficeAssignment { get; set; }
- }
- }
你也可以将多个属性放在同一行上,如下所示:
- public class Instructor
- {
- public int ID { 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")]
- public DateTime HireDate { get; set; }
- [Display(Name = "Full Name")]
- public string FullName
- {
- get { return LastName + ", " + FirstMidName; }
- }
- public virtual ICollection<Course> Courses { get; set; }
- public virtual OfficeAssignment OfficeAssignment { get; set; }
- }
Courses 和OfficeAssignment导航属性
Courses 和OfficeAssignment是导航属性,就像之前解释过的那样,它们通常被定义为virtual类型以便它们可以使用Entity Framework的延迟加载(lazy loading)功能。如果一个导航属性中包含有多个实体,则其类型必须实现ICollection<T>接口,例如List<T>而不是IEnumerable<T>,因为IEnumerable<T>并没有实现Add方法。
一个 instructor可以教多门course,所以Courses 被定义为Course实体的集合。
- public virtual ICollection<Course> Courses { get; set; }
- public virtual OfficeAssignment OfficeAssignment { get; set; }
4.创建OfficeAssignment实体
创建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实体之间是一对零或一对一的关系,office 的指派只和Instructor有关系,因此其主键也是其Instructor实体的外键。但是Entity Framework 不会自动将InstructorID识别为实体的主键,因为该名称并不遵守ID 或者classnameID的命名规范,因此这里使用Key属性来指定该属性为实体的主键。
- [Key]
- [ForeignKey("Instructor")]
- public int InstructorID { get; set; }
ForeignKey属性
当两个实体之间是一对零或一对一关系时(如OfficeAssignment 和Instructor实体间的关系),EF并不能辨别出关系的哪一端是主体,哪一端是依赖。一对一的关系在每一个类中拥有一个对其他类的导航属性的引用。ForeignKey属性可以被应用于依赖类来建立它们之间的关系。如果你省略了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.
Instructor导航属性
Instructor实体有一个值为nullable 的OfficeAssignment导航属性(因为instructor 可能没有被分配office),OfficeAssignment实体有一个值为non-nullable的Instuctor导航属性(因为office不可能在没有instructor 的情况下被分配出去--InstructorID值为non-nullable)。当一个Instructor实体有一个相关联的OfficeAssignment实体时,每个实体在它的导航属性中都有对其它实体的引用。
你可以将Required属性添加到Instructor导航属性来指定必须有一个相关联的Instructor,但是这不是必需的,因为InstructorID外键(同样也是表的主键)是non-nullable的。
5.修改Course实体
打开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 virtual Department Department { get; set; }
- public virtual ICollection<Enrollment> Enrollments { get; set; }
- public virtual ICollection<Instructor> Instructors { get; set; }
- }
- }
DatabaseGenerated属性
CourseID属性的带有None参数的DatabaseGenerated属性指定主键值是由用户提供而不是由数据库生成的。
- [DatabaseGenerated(DatabaseGeneratedOption.None)]
- [Display(Name = "Number")]
- public int CourseID { get; set; }
外键和导航属性
Course 实体中的外键属性和导航属性反映了以下关系:
- 一个course 被分配到一个department,所以该实体中存在一个DepartmentID 外键和一个Department 导航属性。
- public int DepartmentID { get; set; }
- public virtual Department Department { get; set; }
- 一个course可以有任意数量的student选修,所以Enrollments导航属性是一个集合。
- public virtual ICollection<Enrollment> Enrollments { get; set; }
- 一个course可以由多个instructor来讲授,所以Instructors 导航属性也是一个集合。
- public virtual ICollection<Instructor> Instructors { get; set; }
6.创建Department实体
创建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 virtual Instructor Administrator { get; set; }
- public virtual ICollection<Course> Courses { get; set; }
- }
- }
Column属性
之前你使用了Column属性来更改列名,在Department实体的代码中,Column属性被用来更改SQL数据类型映射以便使用SQL Server的money类型来定义该列。
- [Column(TypeName="money")]
- public decimal Budget { get; set; }
外键和导航属性
外键和导航属性反映了如下关系:
- 一个department可能有也可能没有administrator,一个administrator是一个instructor,因此InstructorID属性被作为Instructor实体的外键。在int类型后面添加了问号表示该属性是值可以为nullable。导航属性被命名为Administrator并含有一个Instructor实体。
- public int? InstructorID { get; set; }
- public virtual Instructor Administrator { get; set; }
- 一个department 可以有多门 course,富所以其有一个Courses导航属性
- public virtual ICollection<Course> Courses { get; set; }
注意:基于约定,Entity Framework对于 non-nullable外键和多对多关系会启用级联删除,级联删除规则可能会在你添加迁移时导致异常出现。例如,如果你没有将Department.InstructorID属性定义为nullable,你会得到如下异常信息:"The referential relationship will result in a cyclical reference that's not allowed."。如果你的业务规则需要InstructorID属性可为non-nullable,你必须使用下面的fluent API语句来禁用级联删除。
7.修改Enrollment实体
打开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; }
- }
- }
外键和导航属性
外键和导航属性反映了下列关系:
- 一条enrollment 记录对应一门course,所以有CourseID外键属性和Course导航属性:
- public int CourseID { get; set; }
- public virtual Course Course { get; set; }
- 一条enrollment 记录对应一个student,所以有StudentID外键属性和Student导航属性:
- public int StudentID { get; set; }
- public virtual Student Student { get; set; }
多对多关系
Student 和Course 实体之间有多对多的关系,并且Enrollment 实体作为一个多对多的数据库连接表。这意味着Enrollment表包含了除了连接表外键之外的额外的数据(在本例中是主键和Grade属性)。
下图是实体关系图(此图是由 Entity Framework Power Tools生成的)
每个关系连接线的一端都有个1,另一端是星号,表明这是一个一对多的关系。
如果Enrollment表不包含grade 信息,它只需要有CourseID和StudentID两个外键。在这种情况下,它只对应于数据库中的一个多对多连接表,并且你不需要为它们创建模型类。Instructor和Course实体是多对多关系,但如你所见,它们之间并没有实体类:
在数据库中,连接表是必需的
Entity Framework会自动创建CourseInstructor表,并通过读取和更新Instructor.Course和Course.Instructor导航属性来间接地读取和更新它。
8.在实体关系图中显示关系
下图显示了由Entity Framework Power Tools创建的完整的School 模型:
除了多对多关系连接线(*到*)和一对多关系连接线(1到*),你还可以看到Instructor和OfficeAssignment实体之间的一对零或1关系连接线(1到0..1)和Istructor和Department实体之间的零或一对多(0..1到*)关系连接线。
9.向数据库上下文中添加代码到来自定义数据模型
接下来你将向SchoolContext类中添加新实体并使用fluent API来自定义映射。该API经常被用于在一个语句中同时调用多个方法,如下所示:
- modelBuilder.Entity<Course>()
- .HasMany(c => c.Instructors).WithMany(i => i.Courses)
- .Map(t => t.MapLeftKey("CourseID")
- .MapRightKey("InstructorID")
- .ToTable("CourseInstructor"));
在本文中,你将在不能使用属性的地方使用fluent API来进行数据库映射。但是你也可以如同使用属性那样使用fluent API来指定大部分的格式、验证和映射规则。某些属性比如MinimumLength并不能通过使用fluent API来实现,就像之前提到的那样,MinimumLength不会更改数据库架构,它仅仅用于客户端和服务器端验证。
某些开发人员喜欢只使用fluent API以便他们可以保持他们的实体类"干净"。如果你愿意,你也可以同时使用属性和fluent API,要注意某些自定义功能只能通过使用fluent API来实现,但一般建议是仅选择这两者之一并尽可能的坚持使用下去。
向数据模型中添加新的实体并执行数据库映射,打开DAL\SchoolContext.cs,使用下面的代码替换
- 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"));
- }
- }
- }
- 对于Instructor和Course实体,上面的代码为连接表指定了表名和列名。Code First可以在不使用这段代码的情况下配置多对多关系,但是如果你不使用它,连接表会使用默认名称比如InstructorID列会被命名为InstructorInstructorID。
- 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);
10.向数据库中填充测试数据
打开Migrations\Configuration.cs,使用下面的代码替换
- namespace ContosoUniversity.Migrations
- {
- using ContosoUniversity.Models;
- using ContosoUniversity.DAL;
- using System;
- using System.Collections.Generic;
- using System.Data.Entity;
- using System.Data.Entity.Migrations;
- using System.Linq;
- 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").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 }
- };
- 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").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" },
- };
- officeAssignments.ForEach(s => context.OfficeAssignments.AddOrUpdate(p => p.InstructorID, 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").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();
- }
- 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,
- DepartmentID = departments.Single( s => s.Name == "Engineering").DepartmentID,
- Instructors = new List<Instructor>()
- },
- ...
- };
- courses.ForEach(s => context.Courses.AddOrUpdate(p => p.CourseID, s));
- context.SaveChanges();
11.添加迁移和更新数据库
在PMC中输入add-migration命令(先不要运行update-database命令):
- add-Migration ComplexDataModel
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'.
有时当你在存在数据的情况下执行迁移时,你需要将存根数据插入到数据库以满足外键约束,这就是我们现在要做的。ComplexDataModel中的Up方法为Course表添加一个非空的DepartmentID外键。由于Course表中已存在数据行,SQL Server不知道该向非空列中插入何值,所以AddColumn操作会失败。因此你必须修改代码为新的列提供一个默认值,并创建一个名为"Temp"的存根department 作为默认department 。默认情况下,当运行Up方法时,Course表中已存在的数据行会被关联到"Temp" department ,你可以在Seed方法中将它们关联到正确的department 。
编辑<timestamp>_ComplexDataModel.cs文件,注释掉为Course表添加DepartmentID 列的行,并使用下面的代码替换:
- 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));
编辑完成<timestamp>_ComplexDataModel.cs 文件后,在PMC中输入update-database命令
- update-database
- <add name="SchoolContext" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=CU_Test;Integrated Security=SSPI;"
- providerName="System.Data.SqlClient" />
- update-database -TargetMigration:0
你并没有为CourseInstructor表创建模型类,就像之前解释的那样,它是Instructor 和Course 实体之间多对多关系的连接表。
右键点击CourseInstructor表,选择Show Table Data来验证数据,该数据是通过向Course.Instructors导航属性添加Instructor实体而产生的 。
欢迎转载,请注明文章出处:http://blog.csdn.net/johnsonblog/article/details/39013469