zoukankan      html  css  js  c++  java
  • [翻译][MVC 5 + EF 6] 6:创建更复杂的数据模型

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

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

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

    School_class_diagram

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

    1.1.DateType属性:

      修改ModelsStudent.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 StackOverflow thread

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

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

    Students_index_page_with_formatted_date

    1.2.StringLength属性:

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

      修改ModelsStudent.cs

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

      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,客户端验证将会显示错误提示:

    client side val error

    1.3.Column属性:

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

      修改ModelsStudent.cs

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

      添加了Column属性后,数据库与模型变得不匹配,在Package Manager Console输入命令:

    add-migration ColumnFirstName
    update-database

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

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

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

    2.完成Student实体的修改

    Student_entity

      修改ModelsStudent.cs

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

    2.1.Required属性:

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

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

    2.2.Display属性:

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

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

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

    3.创建Instructor实体

    Instructor_entity

      创建ModelsInstructor.cs

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

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

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

    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"),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实体

    OfficeAssignment_entity

      新建ModelsOfficeAssignment.cs

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

    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实体

    Course_entity

      修改ModelsCourse.cs

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

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

    5.1.DatabaseGenerated属性:

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

    6.新建Department实体

    Department_entity

      创建ModelsDepartment.cs

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

    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实体

    Enrollment_entity

      修改ModelsEnrollment.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模型框架:

    School_data_model_diagram

    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来实现,但是一般情况下推荐的做法是选择两者其中之一然后尽量保持一致性的使用。

      修改DALSchoolContext.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.添加测试数据

      修改MigrationsConfiguration.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 = 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));
            }
        }

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

    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();

      当我们创建一个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: 1)); 
        //AddColumn("dbo.Course", "DepartmentID", c => c.Int(nullable: false));
    
        AlterColumn("dbo.Course", "Title", c => c.String(maxLength: 50));

      在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:0

      打开Server Explorer查看数据表:

      查看CourseInstructor表中的数据:

    Table_data_in_CourseInstructor_table

  • 相关阅读:
    [SDOI2008]Sue的小球
    【洛谷P2611】小蓝的好友
    【YbtOJ#20051】简单游走
    【洛谷P3338】力
    【洛谷P3803】【模板】多项式乘法(FFT)
    【YbtOJ#10053】合影队形
    【YbtOJ#20056】树上取模
    【洛谷P1919】【模板】A*B Problem升级版
    初赛练习总结
    【洛谷P3723】礼物
  • 原文地址:https://www.cnblogs.com/walden1024/p/4586658.html
Copyright © 2011-2022 走看看