[翻译][MVC 5 + EF 6] 11:实现继承

原文:Implementing Inheritance with the Entity Framework 6 in an ASP.NET MVC 5 Application

1.选择继承映射到数据库表

  在School数据模型里面,Instructor和Student类有几个属性是相同的:

  假设我们想要消除Instructor和Student实体属性的冗余代码。或者我们想要编写一个可以格式化name的服务,而不用考虑这个name是来自一个instructor还是student。我们可以创建一个只包含共有属性的Person基类,然后让Instructor和Student实体继承该基类,如下图:

  有几种方法可以把这种继承结构表示在数据库中。我们可以新建一个Person表同时包含student和instructor信息。一些列(HireDate)只用于instructor,一些列(EnrollmentDate)只用于student,一些列(LastName,FirstName)两者均可用。通常,我们需要一个鉴别(discriminator)列来表明每列代表的类型。例如,对于instructor其鉴别列的值可以为“Instructor”,student其鉴别列的值可以为“Student”:

  这种从一张数据表产生实体继承结构的模式被称作table-per-hierarchy(TPH)继承。

  另一种方法使数据库看起来更像继承结构。例如,在Person表中只有name列,在单独的Instructor和Student表中有date列:

  这种为每个实体类产生数据表的模式称为table per type(TPT)继承。

  还有一个选择是所有非抽象类型映射到单独的表。类的所有属性,包括继承属性映射到相应的表。这种模式称为Table-per-Concrete(TPC)继承。如果对Person,Student和Instructor我们采用TPC继承,Student和Instructor表在实现继承后与之前的表结构没有任何不同。

  在EF中,相对于TPT,TPC和TPH继承模式通常会有比较好的性能,所以我们要做的就是创建一个Person类,然后改变Instructor和Student类继承Person,胎哪家新类到DbContext,然后创建迁移(更多关于如何实现其他继承方式,请参考:Mapping the Table-Per-Type (TPT) InheritanceMapping the Table-Per-Concrete Class (TPC) Inheritance)。

2.创建Person类

  在Models文件夹添加Person.cs

    public abstract class Person
    {
        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; }

        [Display(Name = "Full Name")]
        public string FullName
        {
            get
            {
                return LastName + ", " + FirstMidName;
            }
        }
    }

3.Student和Instructor类继承Person

    public class Instructor : Person
    {
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        [Display(Name = "Hire Date")]
        public DateTime HireDate { get; set; }

        public virtual ICollection<Course> Courses { get; set; }
        public virtual OfficeAssignment OfficeAssignment { get; set; }
    }
    public class Student : Person
    {
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        [Display(Name = "Enrollment Date")]
        public DateTime EnrollmentDate { get; set; }

        public virtual ICollection<Enrollment> Enrollments { get; set; }
    }

4.添加Person实体到模型

  在SchoolContext.cs添加:

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

  这就为EF实现table-per-hierarchy继承添加了所有配置。当数据库更新后,Person表将会取代Student和Instructor表。

5.创建和更新迁移文件

  在Package Manager Console中输入命令:

Add-Migration Inheritance

  此时运行Update-Database命令会报错,因为数据库中有数据,而迁移不知道如何处理他们,错误如下:

Could not drop object ‘dbo.Instructor‘ because it is referenced by a FOREIGN KEY constraint.

  打开Migrations\<timestamp>_Inheritance.cs,修改过Up方法:

public override void Up()
{
    // Drop foreign keys and indexes that point to tables we‘re going to drop.
    DropForeignKey("dbo.Enrollment", "StudentID", "dbo.Student");
    DropIndex("dbo.Enrollment", new[] { "StudentID" });

    RenameTable(name: "dbo.Instructor", newName: "Person");
    AddColumn("dbo.Person", "EnrollmentDate", c => c.DateTime());
    AddColumn("dbo.Person", "Discriminator", c => c.String(nullable: false, maxLength: 128, defaultValue: "Instructor"));
    AlterColumn("dbo.Person", "HireDate", c => c.DateTime());
    AddColumn("dbo.Person", "OldId", c => c.Int(nullable: true));

    // Copy existing Student data into new Person table.
    Sql("INSERT INTO dbo.Person (LastName, FirstName, HireDate, EnrollmentDate, Discriminator, OldId) SELECT LastName, FirstName, null AS HireDate, EnrollmentDate, ‘Student‘ AS Discriminator, ID AS OldId FROM dbo.Student");

    // Fix up existing relationships to match new PK‘s.
    Sql("UPDATE dbo.Enrollment SET StudentId = (SELECT ID FROM dbo.Person WHERE OldId = Enrollment.StudentId AND Discriminator = ‘Student‘)");

    // Remove temporary key
    DropColumn("dbo.Person", "OldId");

    DropTable("dbo.Student");

    // Re-create foreign keys and indexes pointing to new table.
    AddForeignKey("dbo.Enrollment", "StudentID", "dbo.Person", "ID", cascadeDelete: true);
    CreateIndex("dbo.Enrollment", "StudentID");
}

  (如果我们使用的是GUID而不是integer作为主键的类型,student的主键值可以不必更改,一些步骤可以省略)

  运行update-database命令。

  (如果在产品上,为了确保万一需要修改回之前的数据库版本,我们在Down方法里也要做相应的修改)

6.测试

  新数据库的结构:

7.部署到Azure

更多关于继承,请查看:TPT Inheritance PatternTPH Inheritance Pattern

时间: 2024-10-27 17:19:19

[翻译][MVC 5 + EF 6] 11:实现继承的相关文章

[翻译][MVC 5 + EF 6] 1:准备工作

原文:Getting Started with Entity Framework 6 Code First using MVC 5 1.新建MVC项目: 2.修改Views\Shared\_Layout.cshtml: <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=d

[翻译][MVC 5 + EF 6] 7:加载相关数据

原文:Reading Related Data with the Entity Framework in an ASP.NET MVC Application 1.延迟(Lazy)加载.预先(Eager)加载.显式(Explicit)加载: EF加载相关数据到实体导航属性有以下几种方式: 延迟加载:当实体第一次读取时,相关数据没有加载.当第一次试图访问导航属性时,所需的导航数据自动加载.这导致多条查询语句被发送到数据库:一条查询实体本身,一条查询实体相关数据.DbContext类默认启用延迟加载

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

原文:Creating a More Complex Data Model for an ASP.NET MVC Application 前面的教程中,我们使用的是由三个实体组成的简单的数据模型.在本教程中,我们将添加更多的实体和关系,并通过指定格式.验证和数据库映射规则来自定义数据模型.有两种方式来定义数据模型:一种是给实体类添加属性,另一种是在数据库上下文类添加代码. 当我们完成后,实体类将完成下图所示的数据模型: 1.通过使用属性来自定义数据模型: 1.1.DateType属性: 修改Mo

[翻译][MVC 5 + EF 6] 5:Code First数据库迁移与程序部署

原文:Code First Migrations and Deployment with the Entity Framework in an ASP.NET MVC Application 1.启用Code First迁移: 当我们开发一个新的程序时,数据模型经常会发生改变,每次模型发生改变时,就会变得与数据库不同步.我们之前配置EF在每次数据模型发生改变时自动删除然后重建数据库.当我们增加.删除或者改变实体类或者改变DbContext类时,在程序下次运行时将会自动删除已经存在的数据库,并且创

[翻译][MVC 5 + EF 6] 4:弹性连接和命令拦截(Command Interception)

原文:Connection Resiliency and Command Interception with the Entity Framework in an ASP.NET MVC Application [注:本节教程可以选择性学习] 本节教程将学习EF6的两个重要特性,这两个特性在我们将程序部署在云环境时特别有用: 弹性连接(connection resiliency):遇到瞬时的连接错误时自动重试连接. 命令拦截(command interception):捕获所有发送到数据库的查询

[翻译][MVC 5 + EF 6] 10:处理并发

原文:Handling Concurrency with the Entity Framework 6 in an ASP.NET MVC 5 Application 1.并发冲突: 当一个用户编辑一个实体数据时,另一个用户在第一个用户的改变提交到数据库之前同时也在编辑这个实体数据,这时就会发生冲突.如果不处理这种冲突,最后更新数据库的用户的更改将覆盖其他用户的修改.在许多程序中,这种风险是可以接受的:如果程序具有较少的用户和较少的更新操作,或者不是覆盖关键的变化,这种情况下处理并发的成本可能大

[翻译][MVC 5 + EF 6] 9:异步和存储过程

原文:Async and Stored Procedures with the Entity Framework in an ASP.NET MVC Application 1.为什么使用异步代码: 一个服务器可用的线程数量是有限的,在高负载的情况下所有的可用线程都可能在使用.在这种情况下,在线程被释放之前服务器不能处理新的请求.在同步代码中,很多线程被占用但是实际上没有做任何操作,因为它们在等待I/O完成.在异步代码中,当一个进程在等待I/O完成的过程中,它的线程将会被释放用于处理其他请求.这

[翻译][MVC 5 + EF 6] 8:更新相关数据

原文:Updating Related Data with the Entity Framework in an ASP.NET MVC Application 1.定制Course的Create和Edit页面: 修改CourseController.cs的Create和Edit方法: public ActionResult Create() { PopulateDepartmentsDropDownList(); return View(); } [HttpPost] [ValidateAnt

[翻译][MVC 5 + EF 6] 3:排序、过滤、分页

原文:Sorting, Filtering, and Paging with the Entity Framework in an ASP.NET MVC Application 1.添加排序: 1.1.修改Controllers\StudentController.cs的Index: public ActionResult Index(string sortOrder) { ViewBag.NameSortParm = String.IsNullOrEmpty(sortOrder) ? "na