MVC5+EF6--6 创建更复杂的数据模型

近期学习MVC5+EF6,找到了Microsoft的原文,一个非常棒的系列,Getting Started with Entity Framework 6 Code First using MVC 5,网址:http://www.asp.net/mvc/overview/getting-started/getting-started-with-ef-using-mvc/creating-an-entity-framework-data-model-for-an-asp-net-mvc-application。

这个系列的原文,可以上面网址找到。我也从网上找到了相关的译文。申明,这些译文,我不是原创,而是从网上找来的,为避免下次还要网上查询,现将这些译文整理放在上面。以后找时间将原文地址附上。

原文网址:

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 特性

在 InstructorOfficeAssignment 实体之间是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导航属性.
  • A course 有多个 student注册登记,因此 Enrollments 导航属性是一个集合:
  • A course 可能由 instructors讲授, 因此 Instructors 导航属性是集合:
·         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; }

创建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会为数据库的列选择适当的类型。这里只是进一步明确了数据库要使用的类型。

外键和导航属性

外键和导航属性如下:

  • 一个部门可能有一个管理员,管理员也是老师,因此这里有如下外键和属性:
  • A department 可以有多门 courses, 因此有一个 Courses 导航属性
·         public int? InstructorID { get; set; }
public virtual Instructor Administrator { get; set; }
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导航属性:
  • 一条登记信息对应一个学生,因此有 StudentID 外键属性 Student导航属性:
·         public int CourseID { get; set; }
public virtual Course Course { get; set; }
·         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以保持他们的代码“干净”。

为了添加新实体到数据模型同时定制通过特性无法实现的数据库映射T使用如下代码替换 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"));
      }
   }
}

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

时间: 2024-11-02 20:04:03

MVC5+EF6--6 创建更复杂的数据模型的相关文章

[渣译文] 使用 MVC 5 的 EF6 Code First 入门 系列:为ASP.NET MVC应用程序创建更复杂的数据模型

这是微软官方教程Getting Started with Entity Framework 6 Code First using MVC 5 系列的翻译,这里是第六篇:为ASP.NET MVC应用程序创建更复杂的数据模型 原文:Creating a More Complex Data Model for an ASP.NET MVC Application 译文版权所有,谢绝全文转载--但您可以在您的网站上添加到该教程的链接. 在之前的教程中您已经创建了由三个实体组成的简单的数据模型.在本教程中

EntityFramework_MVC4中EF5 新手入门教程之四 ---4.在EF中创建更复杂的数据模型

在以前的教程你曾与一个简单的数据模型,由三个实体组成.在本教程中,您将添加更多的实体和关系,并通过指定格式. 验证和数据库映射规则,您将自定义数据模型.你会看到自定义的数据模型的两种方式: 通过添加属性,实体类并通过将代码添加到数据库上下文类. 当您完成时,实体类将已完成的数据模型中,如下图所示: 通过使用属性进行自定义的数据模型 在本节中,您会看到如何通过使用指定的格式,验证和数据库映射规则的属性来自定义数据模型.然后在以下各节,您将创建的几个完整的School数据模型,通过添加属性的类已创建

[翻译][MVC 5 + EF 6] 6:创建更复杂的数据模型

原文:Creating a More Complex Data Model for an ASP.NET MVC Application 前面的教程中,我们使用的是由三个实体组成的简单的数据模型.在本教程中,我们将添加更多的实体和关系,并通过指定格式.验证和数据库映射规则来自定义数据模型.有两种方式来定义数据模型:一种是给实体类添加属性,另一种是在数据库上下文类添加代码. 当我们完成后,实体类将完成下图所示的数据模型: 1.通过使用属性来自定义数据模型: 1.1.DateType属性: 修改Mo

MVC5 + EF6 + Bootstrap3 (10) 数据查询页面

文章来源:Slark.NET-博客园 http://www.cnblogs.com/slark/p/mvc5-ef6-bs3-get-started-search-page.html 上一节:MVC5 + EF6 + Bootstrap3 (9) HtmlHelper用法大全(下) 源码下载:点我下载 目录 前言 创建Model 创建数据库上下文DbContext 创建初始化数据 创建Controller 创建View 查看结果页面 查看数据库 结尾 前言 前面铺垫了那么多,今天我们就用MVC5

[实战]MVC5+EF6+MySql企业网盘实战(23)——文档列表

写在前面 上篇文章实现了图片列表,这篇文章实现文档列表将轻车熟路,因为逻辑基本相似,只是查询条件的不同.这里将txt,doc,docx,ppt,pptx,xls,xlsx的文件都归为文档列表中. 系列文章 [EF]vs15+ef6+mysql code first方式 [实战]MVC5+EF6+MySql企业网盘实战(1) [实战]MVC5+EF6+MySql企业网盘实战(2)——用户注册 [实战]MVC5+EF6+MySql企业网盘实战(3)——验证码 [实战]MVC5+EF6+MySql企业

构建ASP.NET MVC5+EF6+EasyUI 1.4.3+Unity4.x注入的后台管理系统(51)-系统升级

系统很久没有更新内容了,最近花了2个月的时间每天一点点,从原有系统 MVC4+EF5+UNITY2.X+Quartz 2.0+easyui 1.3.4无缝接入 MVC5+EF6+Unity4.x+Quartz 2.3 +easyui 1.4.3. 并以easyui 1.4.3的gray皮肤为基础,升级10个扁平化皮肤 皮肤查看地址 更新的主要目的:新的MVC5特性和更好的性能 记录一下升级过程. 1.除了web层,其他全部提取. 2.新建解决方案.以前命名空间为App.现在更名为Apps. 3.

MVC5+EF6 入门完整教程九

前一阵子临时有事,这篇文章发布间隔比较长,我们先回顾下之前的内容,每篇文章用一句话总结重点. 文章一 MVC核心概念简介,一个基本MVC项目结构 文章二 通过开发一个最基本的登录界面,介绍了如何从Controller中获取表单数据 文章三 EF的整个开发过程 文章四 EF基本的CRUD和常用的HtmlHelper 文章五 使用布局页(模板页)改造UI 文章六 分部视图(Partial View) 文章七 排序过滤分页 文章八 不丢失数据进行数据库结构升级 以上如果有不清楚的可以再回去看一下. 文

MVC5 + EF6 简单示例

本文所使用的软件及环境: Visual Studio Ultimate 2013 (下载地址:http://www.visualstudio.com/downloads/download-visual-studio-vs); MVC5 + EF6 + .NET Framework 4.5 + LocalDB; Windows 7 x64 Professional 说明: 在EF (Entity Framework,以下简称EF6)框架下,操作数据的方式有三种:Database First, Mo

[实战]MVC5+EF6+MySql企业网盘实战(17)——思考2

写在前面 今天吃饭回来,突然有一个更好的想法,这里做一下记录. 系列文章 [EF]vs15+ef6+mysql code first方式 [实战]MVC5+EF6+MySql企业网盘实战(1) [实战]MVC5+EF6+MySql企业网盘实战(2)——用户注册 [实战]MVC5+EF6+MySql企业网盘实战(3)——验证码 [实战]MVC5+EF6+MySql企业网盘实战(4)——上传头像 [Bootstrap]modal弹出框 [实战]MVC5+EF6+MySql企业网盘实战(5)——登录界