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

时间:2022-09-04 22:45:08

原文:Creating a More Complex Data Model for an ASP.NET MVC Application

  前面的教程中,我们使用的是由三个实体组成的简单的数据模型。在本教程中,我们将添加更多的实体和关系,并通过指定格式、验证和数据库映射规则来自定义数据模型。有两种方式来定义数据模型:一种是给实体类添加属性,另一种是在数据库上下文类添加代码。

  当我们完成后,实体类将完成下图所示的数据模型:

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

1.通过使用属性来自定义数据模型

1.1.DateType属性:

  修改Models\Student.cs

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

  DateType属性用于指定比数据库内在类型更具体的类型。在上面的例子中,我们只希望追踪日期,而不是日期和时间。DateType枚举类型提供了许多数据类型,比如Date, Time, PhoneNumber, Currency, EmailAddress等。DateType属性还可以使应用程序能够自动提供特定类型的功能。比如,一个mailto:链接可以被创建为DataType.EmailAddress,可以为一个日期选择器提供HTML 5支持的DataType.Date类型。DateType属性向HTML 5添加data-(发音是data dash)属性,这个属性可以被HTML 5浏览器识别。DateType属性不提供任何验证。

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

  DisplayFormat属性用来显示地指定日期格式:

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

  ApplyFormatInEditMode指定设定的格式同样应用于编辑时的显示格式(有时我们也需要将其设置为false,比如货币值,我们编辑时不希望出现货币符号)。

  我们可以只添加DisplayFormat属性,但是一般情况下最好也要把DataType属性同时加上。DataType属性传递数据的语义而不是如何呈现在屏幕上,具有DisplayFormat属性不具备的以下优点:

  • 浏览器可以支持HTML5特性(比如显示日历控件,区域货币符号,电子邮件链接,一些客户端输入验证等)。
  • 默认情况下,浏览器将基于我们的区域设置显示呈现正确的数据格式。
  • DataType属性可以让MVC选择正确的字段模板来呈现数据(DisplayFormat使用字符串模板)。更多信息请查看:ASP.NET MVC 2 Templates

  当我们对一个日期字段使用DataType属性时,我们还必须指定DisplayFormat属性以确保字段在Chrome浏览器中正确呈现。更多信息请查看:this * thread

  关于如何在MVC中处理其他的日期格式的更多信息,请查看MVC 5 Introduction: Examining the Edit Methods and Edit View,并在该页面搜索“internationalization”。

  运行程序在Index页面,可以看到日期格式发生改变:

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

1.2.StringLength属性:

  我们同时可以使用属性来指定数据的验证规则和验证错误信息。StringLength属性可以设置数据库中字段的最大长度,同时为ASP.NET MVC提供客户端和服务端验证。我们也可以指定字符串的最短长度,但是最短长度值不会对数据库结构产生影响。

  修改Models\Student.cs

    public class Student
{
public int ID { get; set; }
[StringLength()]
public string LastName { get; set; }
[StringLength(, 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).

  数据库模型已经发生改变,因此需要修改数据库结构,并且EF检测到了这种改变。我们使用迁移更新数据库时,我们通过UI添加到数据库中的数据不会丢失。但是Seed方法产生的数据会变回原来的值,因为我们在Seed方法中使用的是AddOrUpdate(AddOrUpdate与数据库术语“upsert”等价)。

  在Package Manager Console输入以下命令:

add-migration MaxLengthOnNames
update-database

  add-migration命令产生一个名为<timeStamp>_MaxLengthOnNames.cs的文件。该文件包含的Up方法将会更新数据库与现在的数据模型匹配。update-database命令则执行产生的代码。

  迁移文件夹名字前面的时间戳被EF用来排定迁移顺序。我们可以在执行update-database命令前创建多个迁移,迁移将会按照我们创建的顺序依次执行。

  运行Create页面,输入名字超过50字符,点击Create,客户端验证将会显示错误提示:

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

1.3.Column属性:

  我们同样可以使用属性来控制类和属性如何映射到数据库。加入我们把first-name列命名为FirstMidName,因为该列可能同时包含middle name。但是我们却希望在数据库中该列的名字为FirstName,因为专门编写查询的用户比较习惯这个名字。对于这种映射,我们可以使用Column属性。

  修改Models\Student.cs

    public class Student
{
public int ID { get; set; }
[StringLength()]
public string LastName { get; set; }
[StringLength(, 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属性后,数据库与模型变得不匹配,在Package Manager Console输入命令:

add-migration ColumnFirstName
update-database

  在Server Explorer,双击Student表打开表设计器。

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

  下面的图片是前两次迁移之前的表。除了表名的改变外,name列的类型由Max变为50:

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

  我们也可以使用Fluent API改变数据库映射,在稍后的教程中将会讲到。

2.完成Student实体的修改

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

  修改Models\Student.cs

        public int ID { get; set; }
[Required]
[StringLength()]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Required]
[StringLength(, 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; }

2.1.Required属性:

  Required属性,表示值不能为空。我们可以使用StringLength的最小长度来替代Required属性:

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

2.2.Display属性:

  Display属性指定该列在文本框中显示的内容。

2.3.Calculated属性(与前面的属性不同,这里的属性指的是类的属性):

  FullName是Calculated属性,它由其他属性产生。因为该属性只有get访问器,所以FullName不会在数据库中产生一列。

3.创建Instructor实体

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

  创建Models\Instructor.cs

    public class Instructor
{
public int ID { get; set; } [Required]
[Display(Name = "Last Name")]
[StringLength()]
public string LastName { get; set; } [Required]
[Column("FirstName")]
[Display(Name = "First Name")]
[StringLength()]
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; }
}

  我们会发现Instructor和Student有几个属性是相同的。在后续的教程中我们将会重构代码消除冗余。

  多个属性可以放在同一行,因此也可以这样:

public class Instructor
{
public int ID { get; set; } [Display(Name = "Last Name"),StringLength(, MinimumLength=)]
public string LastName { get; set; } [Column("FirstName"),Display(Name = "First Name"),StringLength(, MinimumLength=)]
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; } [Display(Name = "Full Name")]
public string FullName
{
get { return LastName + ", " + FirstMidName; }
} public virtual ICollection<Course> Courses { get; set; }
public virtual OfficeAssignment OfficeAssignment { get; set; }
}

  如果导航属性可以包含多个实体,它的类型必须实现ICollection<T>接口,例如IList<T>是允许的,但是IEnumerable<T>是不允许的,因为IEnumerable<T>没有实现Add。

4.创建OfficeAssignment实体

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

  新建Models\OfficeAssignment.cs

    public class OfficeAssignment
{
[Key]
[ForeignKey("Instructor")]
public int InstructorID { get; set; }
[StringLength()]
[Display(Name = "Office Location")]
public string Location { get; set; } public virtual Instructor Instructor { get; set; }
}

4.1.Key属性:

  Instructor和OfficeAssignment是1对0..1关系。一个OfficeAssignment只存在于被分配的Instructor中,因此它的主键同时也是Instructor的外键。但是EF不能自动将InstructorID识别为OfficeAssignment的主键,因为它没有遵从ID或classnameID的命名约定。Key属性用来标识它是主键。

  如果一个实体确实有主键但是我们没有把它命名为ID或classnameID形式,那么我们就可以使用Key属性。默认情况下EF会把这个主键当作不是数据库产生的,因为该列是用来标识关系的。

4.2.ForeignKey属性:

  当两个实体间是1对0..1或者1对1关系时,EF不能确定哪个是本体哪个是依赖。1对1关系中每个类中都会有另一个类的导航属性。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.

  稍后我们将会学习如何使用fluent API来配置这种关系。

5.修改Course实体

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

  修改Models\Course.cs

   public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; } [StringLength(, MinimumLength = )]
public string Title { get; set; } [Range(, )]
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; }
}

  course实体拥有DepartmentID外键属性,并且有Department导航属性。当有相关实体的导航属性时,我们就可以不给数据模型添加ForeignKey属性。EF会在需要的时候自动在数据中添加啊外键。例如,当我们取得一个course实体并编辑它的时候,如果我们不加载Department实体,它将会是空的,因此当我们更新course实体时,我们必须首先获取Department实体。当外键属性DepartmentID包含在数据模型中时,我们在更新之前就不用获取Department。

5.1.DatabaseGenerated属性:

  CourseID属性上的None参数的DatabaseGenerated属性指出,主键的值由用户提供而不是由数据库产生。

6.新建Department实体

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

  创建Models\Department.cs

   public class Department
{
public int DepartmentID { get; set; } [StringLength(, MinimumLength=)]
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; }
}

6.1.Column属性:

  之前我们使用Column属性改变列名映射。在上面的代码中,Column属性被用来改变SQL数据类型映射,这样该列会被定义成SQL的money类型。

  一般情况下列映射是不需要的,因为EF通常会根据我们为属性定义的CLR类型选择适当的SQL Server数据类型。例如,CLR的decimal类型对应SQL Server类型。但是在本例中,该列表示货币,因此money类型更合适。关于CLR数据类型以及它们如何与SQL Server数据类型匹配的更多信息请参考:SqlClient for Entity FrameworkTypes

  按照惯例,EF对非空外键和多对多关系支持级联删除。这可能会导致循环级联删除,当我们在迁移时可能会引起异常。例如,如果我们没有定义Department.InstructorID是可空的,我们将会获得如下异常信息:The referential relationship will result in a cyclical reference that's not allowed。如果我们的业务规则要求InstructorID非空,我们必须使用下面的fluent API语句来禁用级联删除:

modelBuilder.Entity().HasRequired(d => d.Administrator).WithMany().WillCascadeOnDelete(false);

7.修改Enrollment实体

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

  修改Models\Enrollment.cs

    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在数据库中是Student和Course的中间表,如果Enrollment表没有包含Grade信息,那么我们就可以不创建Enrollment。就像Instructor和Course实体也是多对多关系,但是它们没有中间实体。

  EF会自动在数据库中创建CourseInstructor表,我们可以使用Instructor.CoursesCourse.Instructors导航属性直接读取和更新这个表。

8.用实体关系图显示关系

  下面的图显示了EF Power Tools创建的完整的School模型框架:

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

9.在数据库上下文添加自定义数据模型的代码

  下面,我们将会在SchoolContext中添加实体,并使用fluent API来自定义一些映射。这个API被叫做“fluent”是因为它将一系列的方法调用串在一起成一个声明,例如:

 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,这样会保持实体类的“clean”。我们可以将属性和fluent API混合使用,虽然有一些自定义设置只能通过fluent来实现,但是一般情况下推荐的做法是选择两者其中之一然后尽量保持一致性的使用。

  修改DAL\SchoolContext.cs的代码来添加实体到数据模型并且不使用属性来完成数据库映射:

   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实体的多对多关系中,代码指定了连接表的表名和列名。如果没有这些代码,Code First可以自动为我们配置多对多关系,但是如果没有这些代码,Code First产生的是默认的名字,例如InstructorID列的名字可能会是InstructorInstructorID

  下面的代码提供了我们如何使用fluent API取代属性来指定Instructor和OfficeAssignment实体关系的例子:

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

  更多关于Fluent API的信息请查看:Fluent API

10.添加测试数据

  修改Migrations\Configuration.cs

    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 = ,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Abercrombie").ID },
new Department { Name = "Mathematics", Budget = ,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Fakhouri").ID },
new Department { Name = "Engineering", Budget = ,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Harui").ID },
new Department { Name = "Economics", Budget = ,
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 = , Title = "Chemistry", Credits = ,
DepartmentID = departments.Single( s => s.Name == "Engineering").DepartmentID,
Instructors = new List<Instructor>()
},
new Course {CourseID = , Title = "Microeconomics", Credits = ,
DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID,
Instructors = new List<Instructor>()
},
new Course {CourseID = , Title = "Macroeconomics", Credits = ,
DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID,
Instructors = new List<Instructor>()
},
new Course {CourseID = , Title = "Calculus", Credits = ,
DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID,
Instructors = new List<Instructor>()
},
new Course {CourseID = , Title = "Trigonometry", Credits = ,
DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID,
Instructors = new List<Instructor>()
},
new Course {CourseID = , Title = "Composition", Credits = ,
DepartmentID = departments.Single( s => s.Name == "English").DepartmentID,
Instructors = new List<Instructor>()
},
new Course {CourseID = , Title = "Literature", Credits = ,
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));
}
}

  大部分代码和第一篇教程相同,只是简单的更新和插入新的实体对象,并且将相同的数据载入属性作为测试需要。然而注意和Instructor实体有多对多关系的Course实体,做了以下处理:

var courses = new List<Course>
{
new Course {CourseID = , Title = "Chemistry", Credits = ,
DepartmentID = departments.Single( s => s.Name == "Engineering").DepartmentID,
Instructors = new List<Instructor>()
},
// ...
};
courses.ForEach(s => context.Courses.AddOrUpdate(p => p.CourseID, s));
context.SaveChanges();

  当我们创建一个Course实体时,我们使用一个空的集合来初始化Instructors导航属性。这样可以使用Instructors.Add添加与该Course相关的Instructor实体。如果我们没有创建空的列表,我们将不能添加这些关系,因为Instructors属性是空的,不能使用Add方法。我们也可以列表的把初始化放在构造函数里面。

11.添加迁移并更新数据库

  在PMC中输入add-migration命令(暂时不要输入update-database命令):

add-Migration ComplexDataModel

  如果我们现在运行update-database命令,将会得到如下错误:

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表中已存在插入的数据,因此AddColumn操作将会失败,因为SQL Server不知道该列应该插入什么样的非空值。因此必须修改代码给新增的列指定默认值,并且插入一条名为“Temp”的存根department数据,来作为默认的department。这样,已经存在的Course行数据在Up方法运行后将会与“Temp”department关联。

  修改<timestamp>_ComplexDataModel.cs文件:

   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: ));
//AddColumn("dbo.Course", "DepartmentID", c => c.Int(nullable: false)); AlterColumn("dbo.Course", "Title", c => c.String(maxLength: ));

  在PMC输入命令:

update-database

  说明:

    在迁移数据和改变数据库架构时可能还会遇到其他错误。如果遇到我们无法解决的迁移错误,我们可以修改连接字符串的数据库名或者删除数据库。最简单的做法是修改数据库名,例如下面:

 <add name="SchoolContext" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=CU_Test;Integrated Security=SSPI;"
providerName="System.Data.SqlClient" />

    关于如何删除数据库请查看:How to Drop a Database from Visual Studio 2012

    如果重命名数据库依然失败,另一个方法是使用下面命令重新初始化数据库:

update-database -TargetMigration:

  打开Server Explorer查看数据表:

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

  查看CourseInstructor表中的数据:

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

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

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

    这是微软官方教程Getting Started with Entity Framework 6 Code First using MVC 5 系列的翻译,这里是第六篇:为ASP.NET MVC应用程序 ...

  2. 为ASP&period;NET MVC应用程序创建更复杂的数据模型

    为ASP.NET MVC应用程序创建更复杂的数据模型 2014-05-07 18:27 by Bce, 282 阅读, 1 评论, 收藏, 编辑 这是微软官方教程Getting Started wit ...

  3. 翻译:使用 Redux 和 ngrx 创建更佳的 Angular 2

    翻译:使用 Redux 和 ngrx 创建更佳的 Angular 2 原文地址:http://onehungrymind.com/build-better-angular-2-application- ...

  4. EntityFramework&lowbar;MVC4中EF5 新手入门教程之四 ---4&period;在EF中创建更复杂的数据模型

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

  5. &lbrack;翻译&rsqb;&lbrack;MVC 5 &plus; EF 6&rsqb; 7:加载相关数据

    原文:Reading Related Data with the Entity Framework in an ASP.NET MVC Application 1.延迟(Lazy)加载.预先(Eage ...

  6. &lbrack;翻译&rsqb;&lbrack;MVC 5 &plus; EF 6&rsqb; 5:Code First数据库迁移与程序部署

    原文:Code First Migrations and Deployment with the Entity Framework in an ASP.NET MVC Application 1.启用 ...

  7. &lbrack;翻译&rsqb;&lbrack;MVC 5 &plus; EF 6&rsqb; 1:创建数据模型

    原文:Getting Started with Entity Framework 6 Code First using MVC 5 1.新建MVC项目: 2.修改Views\Shared\_Layou ...

  8. &lbrack;翻译&rsqb;&lbrack;MVC 5 &plus; EF 6&rsqb; 11:实现继承

    原文:Implementing Inheritance with the Entity Framework 6 in an ASP.NET MVC 5 Application 1.选择继承映射到数据库 ...

  9. &lbrack;翻译&rsqb;&lbrack;MVC 5 &plus; EF 6&rsqb; 9:异步和存储过程

    原文:Async and Stored Procedures with the Entity Framework in an ASP.NET MVC Application 1.为什么使用异步代码: ...

随机推荐

  1. Webform:Application、ViewState对象的用法

    Application Application对象的作用范围是整个全局,也就是说对所有用户都有效.它在整个应用程序生命周期中都是有效的,类似于使用全局变量一样,所以可以在不同页面中对它进行存取.它和S ...

  2. 使用CSS隐藏HTML元素的4种常用方法

    现在的网页设计越来越动态化,我们经常需要隐藏某些元素,在特定的时候才显示它们.我们通常可以使用4种方法来隐藏和显示元素. 这4种显示和隐藏元素的技术各自有它们自己的优点的缺点,下面来举例说明. 在这篇 ...

  3. EF错误记录

    纯属个人记录错误使用: 1.EntityType“area”未定义键.请为该 EntityType 定义键. 产生原因: 1.命名空间引用错误,可能命名重复导致引用错误 2.实体类无法识别主键或者未设 ...

  4. JavaScript高级编程(一)

    书中第2章,在HTML中使用JavaScript摘要总结 2.1 <script>元素 <script>中的5个属性:charset:可选.表示通过src属性指定的代码的字符集 ...

  5. 不解释,分享这个base&period;css

    @charset "utf-8"; /*! * @名称:base.css * @功能:1.重设浏览器默认样式 * 2.设置通用原子类 */ /* 防止用户自定义背景颜色对网页的影响 ...

  6. Chrome游览器使用时,修改文件和网页刷新后,不能显示效果

    一:因为游览器缓存问题 有时候在写完代码后,刷新游览器,发现自己写的目标是让某一个东西隐藏,但是结果是依旧显示着,打开调试工具在Sources中发现,文件依旧是上次的旧的文件,新文件没有加载进去,无论 ...

  7. 029-IIS配置

    安装IIS.部署网站(发布或者拷贝都可以).修改连接字符串,compilation设为false,删掉cs代码上传文件夹不给执行权限: 在iis管理器中找到上传文件夹,选择属性--执行权限,设置为“无 ...

  8. 20155310 2016-2017-2 《Java程序设计》第五周学习总结

    20155310 2016-2017-2 <Java程序设计>第五周学习总结 教材学习内容总结 •收集对象的行为,像是新增对象的add()方法.移除对象的remove()方法等,都是定义在 ...

  9. AngularJS订阅API服务

    本篇使用AngularJS实现订阅某个API服务. 首页大致是: 其中,what's on显示首页内容,Search通过输入关键词调用API服务显示到页面,MyShows显示订阅的内容. Sarch页 ...

  10. 四大CPU体系结构:ARM、X86&sol;Atom、MIPS、PowerPC

    补充介绍一下RISC:RISC(reduced instruction set computer,精简指令集计算机)是一种执行较少类型计算机指令的微处理器,起源于80年代的MIPS主机(即RISC机) ...