MVC5 Entity Framework学习之实现继承

之前你已经学习了如何处理并发异常,在本节中你将学习如何实现继承。

在面向对象的编程中,你可以使用继承来重用代码。接下来你将修改Instructor和Student类,让它们派生自Person基类,该基类包含instructor和student共有的属性如LastName。你不需要添加或修改任何WEB页面,但是你需要修改某些代码,这些修改会自动反映在数据库中。

映射继承到数据库的选项

School 数据模型中的Instructor和Student类有几个相同的属性:

假设你希望通过共享Instructor和Student实体的属性来减少冗余的代码,或者你希望编写一个服务程序来格式化姓名而不必关心姓名来自instructor还是student。你可以创建一个含有这些共有属性的Person基类,然后让Instructor和Student继承该基类,如下图所示:

在数据库中,这种继承结构可以有多种表现形式。你可以创建一个同时含有student和instructor信息的Person数据库表,该表中的某些列仅适用于instructor(HireDate),某些只适用于student(EnrollmentDate),另外的即可适用于instructor也可适用于student(LastName, FirstName)。通常情况下,你应该创建一个标识列来指明每一行所代表的类型,例如,标识列可以使用"Instructor"来表示instructors ,"Student"来表示students。

这种从单个数据库表生成实体继承结构的模式被称为每个层次结构一个表(table-per-hierarchy)继承。

另一种替代方法是使数据库看起来更像是继承结构,,例如,你可以只在Person表中包含name字段,在Instructor 和Student表中分别包含各自的date字段。

这种为每个实体类都建立一个数据库表的模式被称为每种类型一个表(table per type)继承。

另一种选择是将所有非抽象类型映射到单独的表中。类的所有属性包括继承的,都将映射到相应表中的列,这种模式被称为每个具体的类一个表(Table-per-Concrete Class )继承。如果你为Person,Student和Instructor类实现了TPC 继承,那么Student和Instructor表将与之前的没有什么不同。

在Entity Framework中TPC 和TPH继承模式通常比TPT继承模式具有更好地性能,因为TPT模式可能会产生复杂的连接查询。

本节将教你如何实现TPH继承。TPH继承是Entity Framework默认的继承模式,所以你需要做的就是创建一个Person类,修改Instructor和Student类使其派生自Person类,将新的类添加到DbContext,并创建迁移。

创建Person类

打开Models文件夹,新建Person.cs类,并使用下面的方法替换

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.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

打开Instructor.cs,让Instructor类继承Person类,并删除key 和name字段

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.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; }
    }
}

打开Student.cs,做同样的修改

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.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; }
    }
}

将Person实体类型添加到模型中

打开 SchoolContext.cs,为Person实体类型添加DbSet 属性

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

这就是 Entity Framework配置每个层次结构一个表继承所需要做的修改,稍后你会看到当数据库被更新后,会有一个新建的Person数据表。

创建并更新迁移文件

在 Package Manager Console (PMC)窗口,输入如下命令

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

上面的代码执行了下列数据库更新任务:

移除了Student数据库表的外键约束和索引

  • 将Instructor表重命名为Person并作如下修改来存储Student 数据
    • 为Student 添加了可以为nullable 的EnrollmentDate
    • 添加了标识列来指明该列是一个student 还是instructor
    • 由于student 没有hire date,所以设置HireDate可为nullable
    • 添加一个临时字段用来更新指向student 的外键
  • 将Student表中的数据拷贝到Person表中,这样Student可以得到一个新的主键值
  • 修复了指向student的外键值
  • 重建了外键约束和索引,让它们指向Person表

如果你已经使用了GUID而不是将integer作为主键类型,student 的主键值将不会被更改,同时上面的几个步骤可以被省略。

再次运行update-database命令。

在生产环境中你需要对Down方法进行相应的修改以免你不得不回滚至前一个数据库版本。在本例中不需要使用Down方法。

注意:当迁移数据或更改架构时,你可能会碰到其它的错误,如果你遇到了迁移错误却无法解决,你可以通过修改Web.config文件中的连接字符串或删除已存在的数据库的方法来继续本教程,当然最简单的方法是重新命名Web.config中的数据库。如下所示将数据库名称修改为ContosoUniversity2

<add name="SchoolContext"
    connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=ContosoUniversity2;Integrated Security=SSPI;"
    providerName="System.Data.SqlClient" />

通过使用新建的不存在任何数据的数据库来进行迁移,update-database应该会被成功执行。

测试

运行项目,尝试访问不同的页面,一切运行正常。

打开Server Explorer依次展开Data Connections\SchoolContext \Tables,你可以看到Student和Instructor表已经被Person表替换,打开Person表,你可以看到该表拥有 Student 和Instructor表的所有列。

在Person表上右键单击Show Table Data查看discriminator列

下面是新的School数据库架构

部署到Windows Azure

1.在Visual Studio中,在Solution Explorer上右键选择Publish

2.点击Publish

默认浏览器中会自动打开该站点

3.验证应用程序会否工作正常

当你第一次运行项目并访问数据库时,Entity Framework会自动运行所有迁移中的Up方法来确保数据库架构和当前数据模型一致。

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

欢迎转载,请注明文章出处:http://blog.csdn.net/johnsonblog/article/details/39503915

项目源码:https://github.com/johnsonz/MvcContosoUniversity

还大家一个健康的网络环境,从你我做起

THE END

时间: 2024-08-10 21:22:42

MVC5 Entity Framework学习之实现继承的相关文章

MVC5 Entity Framework学习

MVC5 Entity Framework学习(1):创建Entity Framework数据模型 MVC5 Entity Framework学习(2):实现基本的CRUD功能 MVC5 Entity Framework学习(3):添加排序.筛选和分页功能 MVC5 Entity Framework学习(4):弹性连接和命令拦截 MVC5 Entity Framework学习(5):Code First迁移和部署 MVC5 Entity Framework学习(6):创建复杂的数据模型 MVC5

MVC5 Entity Framework学习之实现主要的CRUD功能

在上一篇文章中,我们使用Entity Framework 和SQL Server LocalDB创建了一个MVC应用程序,并使用它来存储和显示数据.在这篇文章中,你将对由 MVC框架自己主动创建的CRUD(create, read, update, delete)代码进行改动. 注意:通常我们在控制器和数据訪问层之间创建一个抽象层来实现仓储模式.为了将注意力聚焦在怎样使用实体框架上.这里暂没有使用仓储模式. 在本篇文章中,要创建的web页面: watermark/2/text/aHR0cDovL

MVC5 Entity Framework学习之异步和存储过程

在之前的文章中,你已经学习了如何使用同步编程模型来读取和更新数据,在本节中你将学习如何实现异步编程模型.异步可以使应用程序执行更有效率,因为它可以更有效的使用服务器资源. 同样在本节中你还将学习如何针对实体的insert, update, 和delete操作使用存储过程. 最后将应用程序部署到 Windows Azure. 下面是完成后的页面 为什么要使用异步代码 一个web服务器的可用线程是有限的,在高负载情况下,所有的可用线程可能都在被使用.当出现这种情况时,服务器将无法处理新的请求,直到有

MVC5 Entity Framework学习之更新相关数据

在上篇文章中学习了如何在页面中显示相关数据,本节中将学习如何对相关数据进行更新.对于大多数实体关系,可以通过更新外键或导航属性来更新数据,对于多对多关系,Entity Framework不会直接公开连接表,所以你需要通过相应的导航属性来添加和移除实体. 先看完成后的效果图 为Courses自定义Create 和Edit 页面 当一个新的course实体被创建时,该实体必须关联到一个已存在的department.要做到这一点,生成的框架代码应该要包括控制器方法和用于选择department的下列列

MVC5 Entity Framework学习之弹性连接和命令拦截

到目前为止,应用程序一直在本地IIS Express上运行.如果你想让别人通过互联网访问你的应用程序,你必须将它部署到WEB服务器同时将数据库部署到数据库服务器 本篇文章中将教你如何使用在将你的应用程序部署到云环境时的Entity Framework 6的非常有价值的两个特性:弹性连接(瞬时错误的自动重试)和命令拦截(捕获所有发送到数据库的SQL查询语句并记录至日志中). 1.启用弹性连接 当你将应用程序部署到Windows Azure时,相应的数据库部也应被部署到Windows Azure S

MVC5 Entity Framework学习之Entity Framework高级功能

在之前的文章中,你已经学习了如何实现每个层次结构一个表继承.本节中你将学习使用Entity Framework Code First来开发ASP.NET web应用程序时可以利用的高级功能. 在本节中你将重用之前已经创建的页面,接下来你需要新建一个页面并使用原始SQL来批量更新数据库中所有Course的学分. 在Department Edit页面中添加新的验证逻辑并使用非跟踪查询. 执行原始SQL查询 Entity FrameworkCode First API包含有可以让你直接向数据库发送SQ

MVC5 Entity Framework学习之Entity Framework高级功能(转)

在之前的文章中,你已经学习了如何实现每个层次结构一个表继承.本节中你将学习使用Entity Framework Code First来开发ASP.NET web应用程序时可以利用的高级功能. 在本节中你将重用之前已经创建的页面,接下来你需要新建一个页面并使用原始SQL来批量更新数据库中所有Course的学分. 在Department Edit页面中添加新的验证逻辑并使用非跟踪查询. 执行原始SQL查询 Entity FrameworkCode First API包含有可以让你直接向数据库发送SQ

MVC5 Entity Framework学习之读取相关数据

前一篇文章中完成了School 数据模型,接下来你将学习如何读取和显示相关的数据--这里指Entity Framework加载至导航属性中的数据. 下图是完成后的效果图 延迟.预先和显示加载相关数据 Entity Framework可以通过多种方法向实体的导航属性中加载数据 延迟加载(Lazy loading) 当实体第一次被读取时,相关数据并不会被检索.但是,当你第一次访问导航属性时,该导航属性所需的数据会自动加载.这是向数据库发送多个查询语句的结果--一次是读取实体本身,接着是每次与被检索的

MVC5 Entity Framework学习之处理并发

之前你已经学习了如何更新数据,那么在本节你将学习如何在当多个用户在同一时间更新同一实体时处理冲突. 修改与Department实体相关的那些页面以便它们能够i处理并发错误.下面的截图是Index 和Delete页面,以及当出现并发冲突时的错误消息. 并发冲突 当一个用户对实体的数据进行编辑,然后另一个用户在前一个用户将更改写入到数据库之前更新同一实体的数据时将发生并发冲突.如果你没有启用冲突检测,那么最后一次对数据库的更新将会覆盖其他用户对数据库所做的更改.在大部分应用程序中,这种风险是可以接受