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

在以前的教程你曾与一个简单的数据模型,由三个实体组成。在本教程中,您将添加更多的实体和关系,并通过指定格式、 验证和数据库映射规则,您将自定义数据模型。你会看到自定义的数据模型的两种方式: 通过添加属性,实体类并通过将代码添加到数据库上下文类。

当您完成时,实体类将已完成的数据模型中,如下图所示:

School_class_diagram

通过使用属性进行自定义的数据模型

在本节中,您会看到如何通过使用指定的格式,验证和数据库映射规则的属性来自定义数据模型。然后在以下各节,您将创建的几个完整的School数据模型,通过添加属性的类已创建,并在模型中创建新的类,其余的实体类型。

数据类型属性

对于学生入学日期,所有 web 页当前显示的时间以及日期,虽然所有你关心为此字段是日期。通过使用数据批注属性,可以让一个代码将修复中每个视图用于显示数据的显示格式的更改。若要查看示例如何去做你会将属性添加到Student班级中的EnrollmentDate属性。

Models\Student.cs,添加System.ComponentModel.DataAnnotations命名空间的using语句和DataTypeDisplayFormat属性添加的EnrollmentDate属性,如下面的示例所示:

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属性,还可以启用应用程序以自动提供特定于类型的功能。例如, mailto:DataType.EmailAddress,可以创建链接和一个日期选择器可供DataType.Date支持HTML5的浏览器。数据类型属性发出 HTML 5 的浏览器能理解的 HTML 5数据-(发音为数据短划线) 属性。数据类型属性不提供任何验证。

DataType.Date不指定显示日期的格式。默认情况下,根据基于服务器的CultureInfo的默认格式显示的数据字段.

DisplayFormat属性用于显式指定的日期格式:

[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }

ApplyFormatInEditMode设置指定的值显示在文本框中进行编辑时,应该也适用已指定的格式。(您可能不希望一些领域 — — 例如,对于货币值,您可能不希望货币符号在文本框中编辑。)

你可以使用DisplayFormat属性本身,但它通常是一个好的主意,也使用该数据类型的属性。DataType属性传递语义的而不是如何呈现在屏幕上,数据和提供以下好处,你不要跟DisplayFormat:

  • 浏览器可以启用 HTML5 功能 (例如,显示一个日历控件、 适当的区域设置的货币符号、 电子邮件的链接等。)。
  • 默认情况下,浏览器将呈现使用基于您的区域设置的正确格式的数据.
  • 数据类型属性可以启用 MVC 选择权字段模板来呈现的数据 (如果使用了DisplayFormat使用字符串模板)。有关详细信息,请参阅布拉德 · Wilson ASP.NET MVC 2 模板(虽然为 MVC 2 写,这篇文章仍适用于当前版本的 ASP.NET MVC。)

如果您使用日期字段的DataType属性,您必须也指定 DisplayFormat属性,以确保该字段在 Chrome 浏览器中都能正确呈现。更多的信息,请参阅此计算器线程.

再次运行学生索引页,并注意时间将不再显示在注册日期。同样会适用于任何使用Student 模型的视图。

Students_index_page_with_formatted_date

StringLengthAttribute

您还可以指定数据验证规则并使用属性的消息。假设您想要确保用户不输入超过 50 个字符的名称。若要添加此限制,请将StringLength属性添加到LastNameFirstMidName的属性,如下面的示例所示:

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(@"^[A-Z]+[a-zA-Z''-'\s]*$")]

MaxLength属性提供类似的功能,到StringLength属性,但不提供客户端验证。

运行应用程序并单击学生选项卡。您收到以下错误:

自创建数据库,支持 'SchoolContext' 上下文模型已更改。请考虑使用代码第一次迁移来更新数据库 (http://go.microsoft.com/fwlink/?LinkId=238269).

数据库模型已更改,必须在数据库架构中,改变的方式,实体框架检测到。您将使用迁移来更新架构,而不会丢失任何数据,您添加到数据库中使用的 UI。如果您更改由Seed方法创建的数据,这将更改回其原始状态的AddOrUpdate方法,您使用Seed 法。AddOrUpdate是等同于"upsert"操作从数据库术语。

在程序包管理器控制台 (PMC) 中,输入以下命令:

一个dd-migration MaxLengthOnNames
update-database

add-migration MaxLengthOnNames命令创建一个名为< 时间戳 > _MaxLengthOnNames.cs文件。此文件包含将更新数据库,以匹配当前的数据模型的代码。实体框架规定为迁移文件名称预先使用的时间戳用于命令迁移的方法。如果您删除数据库,创建多个迁移后或使用迁移部署项目,所有的迁移应用他们被创建的顺序。

运行创建页上,并输入任一超过 50 个字符的名称。只要你超过 50 个字符,客户端验证可以立即显示一条错误消息。

 client side val error

列属性

此外可以使用属性来控制如何将您的类和属性映射到数据库。假设您已经使用名称FirstMidName为第一个名称字段,因为该字段可能还包含一个中间名。但你想要的数据库列被命名为FirstName,因为将会写对数据库的临时查询的用户已经习惯了这个名字。若要使这种映射,可以使用Column属性。

Column属性指定当创建数据库时,映射到FirstMidName属性的Student表中的列将被命名为FirstName换句话说,当您的代码引用Student.FirstMidName,数据将来自或在Student表的FirstName列中进行更新。如果您不指定列名称,则会给属性名称相同的名称。

添加一条 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 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; }
    }
}

如何添加列属性更改支持 SchoolContext,所以它不会匹配数据库的模型。在 PMC 打造另一次迁移中输入以下命令:

add-migration ColumnFirstName
update-database

服务器资源管理器数据库资源管理器如果您正在使用 Web 快递),双击的学生表。

下图显示了原始的列名称,它正如你在应用前两个迁移之前。除了从FirstMidName 更改为FirstName列名称,两个名称列已从MAX 长度为 50 个字符。

此外可以使数据库映射更改使用Fluent API,您将看到在本教程后面。

如果你尝试编译在您完成所有这些实体类的创建之前,你可能会得到编译器错误。

创建讲师实体

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

请注意的几个属性都是相同的StudentInstructor的实体。在本系列中的晚些时候实施继承教程中,您将重构使用继承来消除这种冗余。

所需和显示属性

LastName属性上的属性指定它是必填的字段,文本框中的标题应该"Last Name"(而不是属性名称,它将"姓氏"与没有空格),和值不能超过 50 个字符。

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

StringLength 属性在数据库中设置的最大长度,并提供客户端和服务器端验证的 ASP.NET MVC。您还可以在此属性中,指定最大字符串长度,但最小值对数据库架构没有任何影响。所需属性双,不需要对值类型 (如 int 的日期时间和浮动。值类型不能分配 null 值,所以他们都是内在要求。你可以移除的所需属性,并替换为StringLength属性的最小长度参数:

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

你可以在一行上,把多个属性,所以你也可以写教练类,如下所示:

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

课程和 OfficeAssignment 导航属性

CoursesOfficeAssignment是导航属性。正如我较早前解释,他们通常定义为虚拟以便他们可以利用实体框架功能叫做延迟加载此外,如果一个导航属性可以容纳多个实体,其类型必须实现ICollection < T >接口。(例如IList < T >资格但不是IEnumerable < T >因为IEnumerable<T>不实现添加.

教练可以教任意数量的课程,使Courses定义为Course实体的集合。我们的业务规则国家教练只能有顶多一个办事处,所以OfficeAssignment定义为一个单一的OfficeAssignment实体 (这可能为null ,如果没有办公室分配)。

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

创建 OfficeAssignment 实体

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

生成项目时,保存您的更改并验证你没有做出任何复制并粘贴了编译器可以捕捉的错误。

键属性

还有一个为零或一个InstructorOfficeAssignment实体之间的关系。分配到办公室只存在着它的分配对象、 讲师,因此其主键也是其Instructor实体的外键。但实体框架因为它的名字并不遵循ID或命名约定的ID自动无法识别InstructorID为此实体的主键。因此, Key属性用于识别的关键:

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

如果该实体有其自身的主键,但您想要有别于classnameIDID名称的属性,还可以使用 Key属性。默认情况下 EF 作为非数据库生成对待关键,因为该列是用来标识的关系。

外键属性

当一个到零或一个关系或一对一关系两个实体 (如OfficeAssignment Instructor之间),EF 不出来哪一端的关系是主体,哪一端是依赖。一对一关系到其他类每个班有引用导航属性。外键属性可以应用于所依赖的类来建立关系。如果您省略外键属性,您会收到以下错误,当您尝试创建迁移:

无法确定主要年底 'ContosoUniversity.Models.OfficeAssignment' 和 'ContosoUniversity.Models.Instructor' 类型之间的关联。本协会主要年底必须显式配置使用关系 fluent API 或数据注释。

稍后在本教程中我们将展示如何使用 fluent API 配置这种关系。

指导员导航属性

Instructor实体具有一个可以为 null 的OfficeAssignment导航属性 (因为教练可能没有分配到办公室),和OfficeAssignment实体具有非可以为 null 的Instructor导航属性 (因为分配到办公室离不开教练 — —InstructorID为非 null 值)。Instructor实体具有OfficeAssignment的相关的实体时,每个实体将具有到另一个在它的导航属性的引用。

你可以把 [Required]的属性对教练导航属性来指定必须有一个相关的教练,但你不必这样做,因为 InstructorID 键 (这也是此表的键) 可以为非空。

修改课程实体

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

课程实体具有外键属性指向相关Department实体的DepartmentID ,它有一个Department导航属性。实体框架并不要求您向您的数据模型中添加外键属性,当你有一个相关的实体的导航属性。EF 会自动在数据库中创建外键,需要他们的地方。但有数据模型中的外键能使更新,更简单、 更高效。例如,当您读取一个课程实体来编辑,Department实体为空如果不加载它,所以当你更新课程的实体,你要首先取各Department实体。当外键属性DepartmentID包含在数据模型中时,你不需要去取各Department实体进行更新之前。

DatabaseGenerated 属性

具有上的CourseID属性参数的DatabaseGenerated 属性指定的主键值都是由用户提供,而不是由数据库生成。

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

默认情况下,实体框架假定由数据库生成的主键值。这是你想在大多数情况下。然而,对于Course的实体,你会为一个部门,2000年系列的另一个部门,使用用户指定的课程号,如 1000年系列,等等。

外键和导航属性

外键属性和Course实体的导航属性反映了以下关系:

  • 一门课程被分配到一个部门,因此DepartmentID键和Department导航属性为上文所述的原因。
    public int DepartmentID { get; set; }
    public virtual Department Department { get; set; }
  • 当然可以有任意数量的学生入学,所以Enrollments导航属性是一个集合:
    public virtual ICollection<Enrollment> Enrollments { get; set; }
  • 可能由多个教师,教一门课程,所以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; }
   }
}

列属性

早些时候你用列属性来更改列名称映射。在各Department实体的代码,将Column属性是被用于更改 SQL 数据类型映射,以便将在数据库中使用 SQL Server money类型定义的列:

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

列映射通常是不必需的因为实体框架通常选择适当的 SQL Server 数据类型基于您定义的属性的 CLR 类型。CLRdecimal类型映射到 SQL Server 的decimal类型。但在这种情况下,你知道列将持有货币金额和货币数据类型是更合适的。

外键和导航属性

外键和导航属性反映以下关系:

  • 一个部门可能有也可能不是管理员,并且管理员总是教练。因此InstructorID属性是作为Instructor的实体,将外键包含和int类型指定要标记为可为空属性后添加一个问号。导航属性被命名为Administrator,但持有Instructor实体:
    public int? InstructorID { get; set; }
    public virtual Instructor Administrator { get; set; }
  • 阿部可能有很多的课程,所以Courses导航属性:
    public virtual ICollection<Course> Courses { get; set; }

按照约定,实体框架启用级联删除非可以为 null 的外键和多对多关系。这可以导致循环的级联删除规则,当您初始值设定项的代码运行时,将导致引发异常。例如,如果您没有将Department.InstructorID属性定义为可以为 null,您将得到下面的异常消息,初始值设定项运行时:"引用关系将导致一个周期性的参考,不允许的"。如果您的业务规则需要作为非可以为 null 的InstructorID属性,您将必须使用以下 fluent API 来禁用级联删除的关系:

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

注册实体

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

多对多关系

有的StudentCourse的实体,与Enrollment实体职能作为多多联接表与有效载荷在数据库中的多对多关系。这意味着Enrollment表包含附加数据除了 (在这种情况下,主键和某一Grade楼盘) 联接的表的外键。

下面的插图显示这些关系在实体关系图的外观。(此关系图使用实体框架电动工具生成 ; 创建关系图不是本教程的一部分,它只是被用在这里作为例证。)

Student-Course_many-to-many_relationship

每个关系线条上,另一个,指示一个一对多关系具有 1 在一结束就和一个星号 (*)。

如果Enrollment表不包含等级的信息,它只需要包含CourseIDStudentID的两个外键。在这种情况下,它将对应于多多联接表没有有效载荷(或纯联接表) 在数据库中,和你不必在所有为它创建一个模型类。InstructorCourse的实体都有那种多对多关系,并且正如你所看到的所以两者是没有实体类:

Instructor-Course_many-to-many_relationship

联接表需要在数据库中,但是,如下面的数据库关系图中所示:

Instructor-Course_many-to-many_relationship_tables

实体框架会自动创建CourseInstructor表中,和您读取和更新它间接地通过读取和更新的Instructor.CoursesCourse.Instructors的导航属性。

实体关系图中显示关系

下面的插图显示的关系图中为已完成的学校模型创建实体框架电动工具。

School_data_model_diagram

除了多对多关系线 (* 到 *) 和一个一对多关系线 (1 到 *),这里您可以看到一个为零或一个关系线 (1 到 0..1) 之间的InstructorOfficeAssignment的实体和零-或--一对多关系线 (0..1 到 *)讲师和部门实体之间。

通过将代码添加到数据库上下文中自定义数据模型

接下来您将添加到SchoolContext新的实体类和自定义一些使用fluent API调用的映射。(API 是"流利",因为它常常被串在一起成单个语句的方法调用一系列。)

在本教程中,您将使用 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"));
      }
   }
}

OnModelCreating方法中新的语句配置多多联接表:

  • InstructorCourse实体之间的多对多关系,该代码指定联接表的表和列名称。代码首先可以配置的多对多关系对你来说没有该代码,但如果你不叫它,您将获得InstructorID列的默认名称,如InstructorInstructorID 。

    modelBuilder.Entity<Course>()
        .HasMany(c => c.Instructors).WithMany(i => i.Courses)
        .Map(t => t.MapLeftKey("CourseID")
            .MapRightKey("InstructorID")
            .ToTable("CourseInstructor"));

下面的代码提供的如何你可以有使用 fluent API 而不是属性来指定InstructorOfficeAssignment的实体之间的关系的示例:

modelBuilder.Entity<Instructor>()
    .HasOptional(p => p.OfficeAssignment).WithRequired(p => p.Instructor);

关于"fluent API"语句在幕后做什么的信息,请参阅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")},newInstructor{FirstMidName="Candace",LastName="Kapoor",HireDate=DateTime.Parse("2001-01-15")},newInstructor{FirstMidName="Roger",LastName="Zheng",HireDate=DateTime.Parse("2004-02-12")}};
         instructors.ForEach(s => context.Instructors.AddOrUpdate(p => p.LastName, s));
         context.SaveChanges();var departments =newList<Department>{newDepartment{Name="English",Budget=350000,StartDate=DateTime.Parse("2007-09-01"),InstructorID= instructors.Single( i => i.LastName=="Abercrombie").InstructorID},newDepartment{Name="Mathematics",Budget=100000,StartDate=DateTime.Parse("2007-09-01"),InstructorID= instructors.Single( i => i.LastName=="Fakhouri").InstructorID},newDepartment{Name="Engineering",Budget=350000,StartDate=DateTime.Parse("2007-09-01"),InstructorID= instructors.Single( i => i.LastName=="Harui").InstructorID},newDepartment{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 =newList<Course>{newCourse{CourseID=1050,Title="Chemistry",Credits=3,DepartmentID= departments.Single( s => s.Name=="Engineering").DepartmentID,Instructors=newList<Instructor>()},newCourse{CourseID=4022,Title="Microeconomics",Credits=3,DepartmentID= departments.Single( s => s.Name=="Economics").DepartmentID,Instructors=newList<Instructor>()},newCourse{CourseID=4041,Title="Macroeconomics",Credits=3,DepartmentID= departments.Single( s => s.Name=="Economics").DepartmentID,Instructors=newList<Instructor>()},newCourse{CourseID=1045,Title="Calculus",Credits=4,DepartmentID= departments.Single( s => s.Name=="Mathematics").DepartmentID,Instructors=newList<Instructor>()},newCourse{CourseID=3141,Title="Trigonometry",Credits=4,DepartmentID= departments.Single( s => s.Name=="Mathematics").DepartmentID,Instructors=newList<Instructor>()},newCourse{CourseID=2021,Title="Composition",Credits=3,DepartmentID= departments.Single( s => s.Name=="English").DepartmentID,Instructors=newList<Instructor>()},newCourse{CourseID=2042,Title="Literature",Credits=4,DepartmentID= departments.Single( s => s.Name=="English").DepartmentID,Instructors=newList<Instructor>()},};
         courses.ForEach(s => context.Courses.AddOrUpdate(p => p.CourseID, s));
         context.SaveChanges();var officeAssignments =newList<OfficeAssignment>{newOfficeAssignment{InstructorID= instructors.Single( i => i.LastName=="Fakhouri").InstructorID,Location="Smith 17"},newOfficeAssignment{InstructorID= instructors.Single( i => i.LastName=="Harui").InstructorID,Location="Gowan 27"},newOfficeAssignment{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 =newList<Enrollment>{newEnrollment{StudentID= students.Single(s => s.LastName=="Alexander").StudentID,CourseID= courses.Single(c => c.Title=="Chemistry").CourseID,Grade=Grade.A 
                },newEnrollment{StudentID= students.Single(s => s.LastName=="Alexander").StudentID,CourseID= courses.Single(c => c.Title=="Microeconomics").CourseID,Grade=Grade.C 
                 },newEnrollment{StudentID= students.Single(s => s.LastName=="Alexander").StudentID,CourseID= courses.Single(c => c.Title=="Macroeconomics").CourseID,Grade=Grade.B
                 },newEnrollment{StudentID= students.Single(s => s.LastName=="Alonso").StudentID,CourseID= courses.Single(c => c.Title=="Calculus").CourseID,Grade=Grade.B 
                 },newEnrollment{StudentID= students.Single(s => s.LastName=="Alonso").StudentID,CourseID= courses.Single(c => c.Title=="Trigonometry").CourseID,Grade=Grade.B 
                 },newEnrollment{StudentID= students.Single(s => s.LastName=="Alonso").StudentID,CourseID= courses.Single(c => c.Title=="Composition").CourseID,Grade=Grade.B 
                 },newEnrollment{StudentID= students.Single(s => s.LastName=="Anand").StudentID,CourseID= courses.Single(c => c.Title=="Chemistry").CourseID},newEnrollment{StudentID= students.Single(s => s.LastName=="Anand").StudentID,CourseID= courses.Single(c => c.Title=="Microeconomics").CourseID,Grade=Grade.B         
                 },newEnrollment{StudentID= students.Single(s => s.LastName=="Barzdukas").StudentID,CourseID= courses.Single(c => c.Title=="Chemistry").CourseID,Grade=Grade.B         
                 },newEnrollment{StudentID= students.Single(s => s.LastName=="Li").StudentID,CourseID= courses.Single(c => c.Title=="Composition").CourseID,Grade=Grade.B         
                 },newEnrollment{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();}voidAddOrUpdateInstructor(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导航属性初始化为空的集合,使用代码Instructors = new List<Instructor>()这样一来,我们就可以添加通过使用Instructors.Add方法与本Course相关的Instructor实体。如果您没有创建一个空的列表,您将无法添加这些关系,因为Instructors属性将为 null,并且不会具有一个Add方法。您还可以添加列表初始化到构造函数。

添加迁移和更新数据库

从美国 pmc 公司,输入 add-migration命令:

PM> add-Migration Chap4

如果您尝试更新数据库在这一点上,您将收到以下错误:

ALTER TABLE 语句冲突具有外键约束"FK_dbo。Course_dbo。Department_DepartmentID"。冲突发生在数据库"ContosoUniversity"表"dbo。部",列 'DepartmentID'。

编辑 <时间戳 > _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()
{

(请确保您注释掉或删除现有的AddColumn行,当你添加最新的一个,或当您输入update-database命令时,您将收到错误)。

有时,当您执行迁移的现有数据,您需要将存根 (stub) 数据插入到数据库,以满足外键约束,和这就是你现在在做什么。生成的代码将一个非可以为 null 的DepartmentID外键添加到Course表。如果已有行Course表中的代码运行时,AddColumn操作将失败,因为 SQL Server 不知道要放在不能为空的列的值。因此你已经修改了代码,给新列的默认值,并创建了一个名为"Temp"作为默认部门的存根 (stub) 部门。其结果是,如果存在现有Course行运行此代码时,他们将所有相关的"Temp"部。

Seed方法运行时,它将在 Department表中插入行,它将与那些新的Department行相关Course的现有行。如果您没有添加任何课程在 UI 中,你会然后不再需要"Temp"部门或上Course.DepartmentID列的默认值。若要允许有人可能使用的应用程序添加课程的可能性,你也想更新的Seed方法代码,以确保Course的所有行 (而不仅仅是由早期运行的Seed方法插入) 都具有有效的DepartmentID值之前你从列中删除默认值,并删除"Temp"部。

编辑完后 <时间戳 > _Chap4.cs文件,请在执行迁移的 PMC 输入 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" />
.

服务器资源管理器打开的数据库,像你那样的早些时候,并展开节点以查看所有的表已创建。(如果你仍有服务器资源管理器从更早的时间打开,单击刷新按钮。

您没有创建模型类的CourseInstructor表。如前所述,这是为InstructorCourse的实体之间的多对多关系的联接表。

用鼠标右键单击CourseInstructor表并选择显示表数据来验证它有数据在其中由于Instructor实体添加到Course.Instructors导航属性。

Table_data_in_CourseInstructor_table

摘要

您现在有一个更复杂的数据模型和相应的数据库。在下面的教程中,您将学习更多关于不同的方式来访问相关的数据。

posted @ 2015-02-02 10:40  178mz  阅读(1237)  评论(3编辑  收藏  举报