演练5-7:Contoso大学校园管理系统(实现继承)

在上一次教程中,你已经能够处理并发异常。这个教程将会展示如何在数据模型中实现继承。

在面向对象的程序设计中,你可以通过继承来清除冗余的代码。在这个教程中,你将要通过修改教师 Instructor 和学生 Student 类,以便使他们从包含类似 LastName 属性的 Person 类中派生。对于 Web 页面不需要任何改动,你需要修改一点代码,这些修改将会被自动反射到数据库中。

一、单表继承 (Table-per-Hierarchy)  vs 类型表 (Table-per-Type) 继承

在面向对象的程序设计中,你可以通过对相关的类使用继承来使得工作更加简单。例如,教师 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 继承。你需要完成如下的步骤:

  • 创建 Person 类,将 Instructor 和 Student 类从 Person 类中派生
  • 在数据库上下文类中增加模型到数据库的映射代码
  • 将项目中的 InstructorID 和 StudentID 修改为使用 PersonID.

8-2  创建 Person 类

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

8-3  在模型中增加 Person 实体类型

在 SchoolContext.cs 文件中,对 Person 实体类型增加一个 DbSet 类型的属性。

public DbSet<Person> People { get; set; }

这就是在 EF 中配置 TPH 继承所有需要完成的工作。如你所见,当数据库重建的时候,EF 会自动创建 Person 表。

8-4  修改 InstructorID 和 StudentID 为 PersonID

在 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 作为主键,这里就不需要重新命名 )

8-5  在初始化器中修改主键的值

在 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" },
};

8-6  将 OfficeAssignment 调整为延迟加载

当前版本的 EF 对导航属性使用了 TPH继承模式后的派生类,在一对一,或者一对零的关系上不支持预先加载模式。这对于 Instructor 实体上的 OfficeAssignment 属性是个问题。解决这个问题,需要删除在这个属性上使用的预先加载处理。 在 InstructorController.cs 文件中,删除三次出现的如下代码。

 .Include(i => i.OfficeAssignment)

8-7  测试

运行程序,在各个页面上检查一下,所有的工作如以前一样。 在解决方案管理器上,双击 School.sdf 数据库,在服务器资源管理器中打开,展开 School.sdf,然后选择 Tables,你会看到 Student 和 Instructor 表已经被 Person 表替换掉了。展开 Person 表,你会看到其中拥有原来 Student 和 Instructor 中所有的列,加上 discriminator 列。

下图展示了新的 School 数据库的结构。

对于 Person ,Student 和 Instructor 类,通过 TPH 实现了继承。对于更多的关于其他继承结构,可以查看 Morteza Manavi 的博客 Inheritance Mapping Strategies。在下一次的教程中,将会学习实现仓储和单元模式的一些途径。

时间: 2024-10-07 09:53:43

演练5-7:Contoso大学校园管理系统(实现继承)的相关文章

演练5-4:Contoso大学校园管理系统4

在之前的教程中,我们已经完成了学校的数据模型.现在我们将读取和显示相关数据,请理解EF装载导航属性的方式. 下图显示了我们将要实现的页面效果. 一.Lazy.Eager.Explicit数据加载 使用EF为实体中的导航属性加载相关数据,有下面几种方法. 1.Lazy loading 当实体第一次读取时,相关数据并没有获得.然而当你第一次想要访问导航属性时,导航属性的相关数据会自动获得.这产生了多次访问数据库,一次是实体数据读取,一次是导航属性读取. 2.Eager loading 当实体读取时,

演练5-6:Contoso大学校园管理系统6

在上一次的教程中,我们处理了关联数据问题.这个教程演示如何处理并发问题.你将使用Department实体创建一个页面,这个页面在支持编辑和删除的同时,还可以处理并发错误.下面的截图演示了Index页面和Delete页面,包括在出现并发冲突的时候提示的一些信息. 一.并发冲突 并发冲突出现在这样的时候,一个用户正在显示并编辑一个实体,但是在这个用户将修改保存到数据库之前,另外的一个用户却更新了同样的实体.如果你没有通过EF检测类似的冲突,最后一个更新数据的用户将会覆盖其他用户的修改.例如,一个大客

演练5-1:Contoso大学校园管理1

**演练目的:掌握复杂模型的应用程序开发. Contoso大学校园管理系统功能包括学生.课程.教师的管理. 一.创建MVC Web应用程序 显示效果如下图,操作步骤略. 二.创建数据模型 1.创建学生实体 using System; using System.Collections.Generic; namespace ContosoUniversity.Models { public class Student { public int StudentID { get; set; } publ

Contoso 大学 - 使用 EF Code First 创建 MVC 应用,实例演练

Contoso 大学 Web 示例应用演示了如何使用 EF 技术创建 ASP.NET MVC 应用.示例中的 Contoso 大学是虚构的.应用包括了类似学生注册.课程创建以及教师分配等功能. 这个系列教程展示了创建 Contoso 大学应用的步骤.你可以 下载完整 的程序,或者按照教程一步一步创建它,这个教程中使用 C# 进行演示,下载的代码中同时包含 C# 和 VB 实现.如果你有与这个教程没有直接相关的问题,可以张贴到 ASP.NET Entity Framework forum  或者

Contoso 大学 - 7 – 处理并发

原文地址:http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/handling-concurrency-with-the-entity-framework-in-an-asp-net-mvc-application 全文目录:Contoso 大学 - 使用 EF Code First 创建 MVC 应用 在上一次的教程中我们处理了关联数据问题.这个教程演示如何处理并发问题.你将使用 Department 实体创建一

开源项目1:某大学校友管理系统

申明:所有的本人开源项目仅供学习交流用. 为了方便学习交流,我会慢慢将一些私有的项目开源,有些项目可能开发的时间比较早,会有些欠缺,可以多多交流. IDE:myeclipse 8.5(版本问题不大),web服务器:tomcat 6.0,数据库:sql server 2000/2005,系统架构:struts2+Hibernate+Spring+Ajax 界面篇: 由于下班在即,剩余分析晚上稍后,源码和数据库下载也稍后 开源项目1:某大学校友管理系统

Contoso 大学 - 3 - 排序、过滤及分页

原文地址:http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/sorting-filtering-and-paging-with-the-entity-framework-in-an-asp-net-mvc-application 在上一个课程中,我们已经学习了如何使用 EF 对 Student 实体进行增.删.改.查处理.这次的课程我们将对学生的 Index 页面加入排序.过滤以及分页的功能.还要创建一个页面完成

Contoso 大学 - 5 – 读取关联数据

原文地址:http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/reading-related-data-with-the-entity-framework-in-an-asp-net-mvc-application 全文目录:Contoso 大学 - 使用 EF Code First 创建 MVC 应用 在前面的课程中已经完成了 School 数据模型.在这次的课程中,将要读取和显示相关的数据,这里指的是 EF 通

Contoso 大学 - 10 - 高级 EF 应用场景

原文地址:http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/advanced-entity-framework-scenarios-for-an-mvc-web-application全文目录:Contoso 大学 - 使用 EF Code First 创建 MVC 应用 在上一个教程中,你已经实现了仓储和工作单元模式.这个教程涵盖下列主题: 执行原始的 SQL 查询 执行没有跟踪的查询 检查发送到数据库的查询