标签:style blog http io ar color os 使用 sp
原文地址:http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/implementing-inheritance-with-the-entity-framework-in-an-asp-net-mvc-application在上一次教程中,你已经能够处理并发异常。这个教程将会展示如何在数据模型中实现继承。
在面向对象的程序设计中,你可以通过继承来清除冗余的代码。在这个教程中,你将要通过修改教师 Instructor 和学生 Student 类,以便使他们从包含类似 LastName 属性的 Person 类中派生。对于 Web 页面不需要任何改动,你需要修改一点代码,这些修改将会被自动反射到数据库中。
在面向对象的程序设计中,你可以通过对相关的类使用继承来使得工作更加简单。例如,教师 Instructor 和学生 Student 类在学校 School 数据模型中共享多个属性,带来了冗余的代码。
假设你希望清除在教师 Instructor 和学生 Student 之间所共享的属性带来的冗余代码。可以创建一个 Person 基类,其中仅仅包含他们共享的属性,然后,使得教师 Instructor 和学生 Student 类从 Person 基类派生,如下图所示。
在数据库中这种继承结构可以有多种表现形式,可以创建一个名为 Person 的表,在这个独立的表中包含教师和学生所有的信息。既包括他们独自拥有的属性 ( 例如教师的 HireDate ,以及学生的 EnrollmentDate ),也包括它们共有的属性 ( 例如 LastName, FirstName )。通常你还需要一个用于识别当前类型的列 discriminator 来标识当前行的类型。( 在这里,标识列的内容为 Instructor 来表示教师,Student 来表示学生 )
使用单个数据库表来生成实体继承结构的模式称为单表继承模式 TPH (table-per-hierarchy )。
另外一种方式是使得数据库看起来类似继承结构。例如,在 Person 表中仅仅包含他们共有的名字属性,而将不同的时间分别保存到独立的 Instructor 和 Student 表中。
这种每种实体类对应一张数据库表的模式称为类型表 TPT 继承 (table per type )。
在 EF 中,TPH 继承比 TPT 继承有更好的性能,因为 TPT 继承需要复杂的连接查询。这个教程演示如何实现 TPH 继承。你需要完成如下的步骤:
在 Model 文件夹中,创建 Person.cs ,使用下面的代码替换原有的代码。
using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace ContosoUniversity.Models { public abstract class Person { [Key] public int PersonID { get; set; } [Required(ErrorMessage = "Last name is required.")] [Display(Name="Last Name")] [MaxLength(50)] public string LastName { get; set; } [Required(ErrorMessage = "First name is required.")] [Column("FirstName")] [Display(Name = "First Name")] [MaxLength(50)] public string FirstMidName { get; set; } public string FullName { get { return LastName + ", " + FirstMidName; } } } }
在 Instructor.cs 文件中,将 Instructor 从 Person 派生出来,删除 key 和 name 字段。代码如下所示。
using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace ContosoUniversity.Models { public class Instructor : Person { [DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)] [Required(ErrorMessage = "Hire date is required.")] [Display(Name = "Hire Date")] public DateTime? HireDate { get; set; } public virtual ICollection<Course> Courses { get; set; } public virtual OfficeAssignment OfficeAssignment { get; set; } } }
对 Student.cs 文件进行类似的修改,修改后的 Student 类如下所示。
using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace ContosoUniversity.Models { public class Student : Person { [Required(ErrorMessage = "Enrollment date is required.")] [DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)] [Display(Name = "Enrollment Date")] public DateTime? EnrollmentDate { get; set; } public virtual ICollection<Enrollment> Enrollments { get; set; } } }
在 SchoolContext.cs 文件中,对 Person 实体类型增加一个 DbSet 类型的属性。
public DbSet<Person> People { get; set; }
这就是在 EF 中配置 TPH 继承所有需要完成的工作。如你所见,当数据库重建的时候,EF 会自动创建 Person 表。
在 SchoolContext.cs 文件中,在教师对课程的映射语句中,将 MapRightKey(“InstructorID”) 修改为 MapRightKey(“PersonID”)。
modelBuilder.Entity<Course>() .HasMany(c => c.Instructors).WithMany(i => i.Courses) .Map(t => t.MapLeftKey("CourseID") .MapRightKey("PersonID") .ToTable("CourseInstructor"));
这个修改不是必须的,它仅仅修改了在表多对多连接中 InstructorID 的名字。如果你保留名为仍然为 InstructorID,程序还是可以正常工作的。
下一步,进行一次全局的替换 ( 项目中所有的文件 ),将 InstructorID 修改为 PersonID,StudentID 修改为 PersonID。注意区分大小写。( 这里演示了使用类名加上 ID 作为主键的一个缺点。如果你没有使用类名加上 ID 作为主键,这里就不需要重新命名 )
在 SchoolInitializer.cs 中,代码假设对于 Student 和 Instructor 实体的主键数字是分离的。这对于 Student 实体来说仍然正确 ( 他们仍然从 1 到 8 ),但是对于 Instructor 实体来说,将不再从 1-5 而是修改为 9-13,因为在初始化类中的代码将教师增加在同一张表的学生之后。使用下面的代码修改 Department 和 OfficeAssignment 实体中的代码,以便使用新的教师 ID。
var departments = new List<Department> { new Department { Name = "English", Budget = 350000, StartDate = DateTime.Parse("2007-09-01"), PersonID = 9 }, new Department { Name = "Mathematics", Budget = 100000, StartDate = DateTime.Parse("2007-09-01"), PersonID = 10 }, new Department { Name = "Engineering", Budget = 350000, StartDate = DateTime.Parse("2007-09-01"), PersonID = 11 }, new Department { Name = "Economics", Budget = 100000, StartDate = DateTime.Parse("2007-09-01"), PersonID = 12 } };
var officeAssignments = new List<OfficeAssignment> { new OfficeAssignment { PersonID = 9, Location = "Smith 17" }, new OfficeAssignment { PersonID = 10, Location = "Gowan 27" }, new OfficeAssignment { PersonID = 11, Location = "Thompson 304" }, };
当前版本的 EF 对导航属性使用了 TPH继承模式后的派生类,在一对一,或者一对零的关系上不支持预先加载模式。这对于 Instructor 实体上的 OfficeAssignment 属性是个问题。解决这个问题,需要删除在这个属性上使用的预先加载处理。
在 InstructorController.cs 文件中,删除三次出现的如下代码。
.Include(i => i.OfficeAssignment)
运行程序,在各个页面上检查一下,所有的工作如以前一样。
在解决方案管理器上,双击 School.sdf 数据库,在服务器资源管理器中打开,展开 School.sdf,然后选择 Tables,你会看到 Student 和 Instructor 表已经被 Person 表替换掉了。展开 Person 表,你会看到其中拥有原来 Student 和 Instructor 中所有的列,加上 discriminator 列。
下图展示了新的 School 数据库的结构。
对于 Person ,Student 和 Instructor 类,通过 TPH 实现了继承。对于更多的关于其他继承结构,可以查看 Morteza Manavi 的博客 Inheritance Mapping Strategies。在下一次的教程中,将会学习实现仓储和单元模式的一些途径。
标签:style blog http io ar color os 使用 sp
原文地址:http://www.cnblogs.com/itjeff/p/4140729.html