上篇链接:EF学习笔记(十) 处理并发
本篇原文链接:Implementing Inheritance
面向对象的世界里,继承可以很好的重用代码。在本章就对Instructor和Student两个类进行实施继承处理,这两个类有公用的属性,比如LastName等,也有私有的属性;
可以不用增加任何页面,进行修改一些代码,这些修改后的继承关系就会自动反应到数据库中。
通过下图可以看到两个类有哪些公用属性:
那么就可以建一个Person的基础类,让这两个类继承自Persion类:
Options for mapping inheritance to database tables
映射继承到数据库表有以下3种方式:
TPH: table-per-hierarchy
在数据库里就建一个表,里面除了有公有的字段外,既有Student的私有字段,也有Instructor的私有字段;然后再加上一个辨别字段(discriminator)来标明本行是Student还是Instructor,如下图:
TPT: table per type
在数据库里为基础类、继承后的类各生成一张表,表结构就和继承关系一样;
TPC: Table-per-Concrete
就是把所有非抽象的属性都定义到表中,包括来自继承的基类属性;这种方式生成的数据库表就可以和前面没有写继承的时候是一样的;
TPC 和TPH 理论上会有比较好的性能,因为TPT会有大量复杂的SQL Join的操作;
本篇只讲述TPH,TPT和TPC可通过以下链接学习:
Mapping the Table-Per-Type (TPT) Inheritance
Mapping the Table-Per-Concrete Class (TPC) Inheritance
实践操作
先新建一个Person基类:
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace EFTest.Models { 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; } } } }
然后修改Student和Instructor类,继承自Person类,去除部分代码:
using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace EFTest.Models { 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; } } }
using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace EFTest.Models { 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; } } }
在SchoolContext.cs中增加:
public DbSet<Person> People { get; set; }
下面就开始迁移数据库,在PMC输入: Add-Migration Inheritance
如果这个时候,直接运行 update-database 会有报错,主要是因为有一些外键存在;
在XXXXXXXX_Inheritance.sc中的up方法中加入以下处理代码:
红色字部分为增加的,注意插入的顺序,
主要工作就是删除原有外键,然后增加一列临时列,把原有Student数据放到新表来;
然后把原来的Enrollement的外键值更新一下,变为新的ID值,再把临时列删除,最后把外键和索引重新再建立:
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)); 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"); }
最后执行下 update-database:
最后看看数据库的结果:
最后数据库的结构变为: