Поделиться через


Руководство. Реализация наследования с помощью EF в приложении ASP.NET MVC 5

В предыдущем руководстве вы обрабатывали исключения параллелизма. В этом учебнике демонстрируется, как реализовать наследование в модели данных.

В объектно-ориентированном программировании можно использовать наследование для упрощения повторного использования кода. В рамках этого учебника вы измените классы Instructor и Student таким образом, чтобы они были производными от базового класса Person, который содержит общие свойства для преподавателей и учащихся, такие как LastName. Изменения вносятся в коде, а не на веб-страницах, и автоматически отражаются в базе данных.

В этом учебнике рассмотрены следующие задачи.

  • Сведения о сопоставлении наследования с базой данных
  • Создание класса Person
  • Обновление Instructor и Student
  • Добавление пользователя в модель
  • Создание и обновление миграций
  • Тестирование реализации
  • Развернуть в Azure

Предварительные требования

Сопоставление наследования с базой данных

Классы Instructor и Student в School модели данных имеют несколько идентичных свойств:

Student_and_Instructor_classes

Предположим, что вам требуется исключить повторяющийся код для свойств, которые являются общими для сущностей Instructor и Student. Кроме того, вам может потребоваться написать службу, которая может форматировать имена как преподавателей, так и учащихся. Можно создать базовый Person класс, содержащий только эти общие свойства, а затем сделать сущности Instructor и Student наследующимися от этого базового класса, как показано на следующем рисунке:

Student_and_Instructor_classes_deriving_from_Person_class

Структура наследования может быть представлена в базе данных несколькими способами. У вас может быть таблица Person, содержащая одновременно информацию о преподавателях и учащихся. Некоторые столбцы могут применяться только к преподавателям (HireDate), некоторые — только к учащимся (EnrollmentDate), некоторые — к обоим (LastName, FirstName). Как правило, у вас есть дискриминаторный столбец, указывающий, какой тип представляет каждая строка. Например, в столбце дискриминатора может указываться значение "Instructor" для преподавателей и "Student" для учащихся.

Таблица на hierarchy_example

Этот шаблон создания структуры наследования сущностей из отдельной таблицы базы данных называется наследованием таблицы на иерархию (TPH).

В качестве альтернативы можно создать базу данных, которая будет иметь приближенный к структуре наследования вид. Например, можно хранить в таблице Person только поля с именами и создать отдельные таблицы Instructor и Student с полями дат.

Таблица на type_inheritance

Этот шаблон создания таблицы базы данных для каждого класса сущностей называется наследованием таблицы для каждого типа (TPT).

Кроме того, можно сопоставить все не являющиеся абстрактными типы с отдельными таблицами. Все свойства класса, включая унаследованные, сопоставляются со столбцами в соответствующей таблице. Такая модель называется наследованием типа "одна таблица на конкретный класс". Если реализовать наследование типа "одна таблица на конкретный класс" для показанных выше классов Person, Student и Instructor, таблицы Student и Instructor после реализации наследования будут выглядеть так же, как и до этого.

Шаблоны наследования TPC и TPH обычно обеспечивают более высокую производительность в Entity Framework, чем шаблоны наследования TPT, так как шаблоны TPT могут привести к сложным запросам на соединение.

В этом учебнике демонстрируется реализация модели наследования "одна таблица на иерархию". TPH — это шаблон наследования по умолчанию в Entity Framework, поэтому все, что вам нужно сделать, — это создать Person класс, изменить Instructor классы и Student на производные от Person, добавить новый класс в DbContextи создать миграцию. (Сведения о том, как реализовать другие шаблоны наследования, см. в разделах Сопоставление наследования таблицы на тип (TPT) и Сопоставление наследования наследование конкретного класса (TPC) в документации MSDN Entity Framework.)

Создание класса 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;
            }
        }
    }
}

Обновление Instructor и Student

Теперь обновите файлы Instructor.cs и Student.cs , чтобы наследовать значения от Person.sc.

В Файле Instructor.cs наследуйте Instructor класс от Person класса и удалите поля ключа и имени. Код будет выглядеть следующим образом:

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. Класс Student будет выглядеть следующим образом:

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

Добавление пользователя в модель

В SchoolContext.cs добавьте DbSet свойство для типа сущности Person :

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

Это все, что требуется платформе Entity Framework для настройки наследования типа "одна таблица на иерархию". Как вы увидите, при обновлении базы данных вместо таблиц и Instructor будет содержаться Person таблицаStudent.

Создание и обновление миграций

В консоли диспетчера пакетов (PMC) введите следующую команду:

Add-Migration Inheritance

Update-Database Выполните команду в PMC. На этом этапе команда завершится ошибкой, так как у нас есть данные, которые миграция не знает, как обрабатывать. Появляется следующее сообщение об ошибке:

Не удалось удалить объект 'dbo. Instructor', так как на него ссылается ограничение FOREIGN KEY.

Открыть миграции< 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:

    • Добавляет допускающий значения NULL тип EnrollmentDate для учащихся.
    • Добавляет столбец дискриминатора, который указывает, представляет ли строка учащегося или преподавателя.
    • Изменяет тип HireDate на допускающий значения NULL, поскольку в строках для учащихся не будет указываться дата приема на работу.
    • Добавляет временное поле, которое будет использоваться для обновления внешних ключей, указывающих на учащихся. При копировании учащихся в таблицу Person они получат новые значения первичного ключа.
  • Копирует данные из таблицы Student в таблицу Person. При этом записям учащихся назначаются новые значения первичного ключа.

  • Исправляет значения внешнего ключа, которые указывают на учащихся.

  • Повторно создает ограничения внешнего ключа и индексы, которые после этого указывают на таблицу Person.

(Если вместо целочисленного типа первичного ключа используется GUID, значения первичного ключа для записей учащихся изменять не потребуется и некоторые из этих действий можно пропустить.)

Выполните команду 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 команда с гораздо большей вероятностью завершится без ошибок. Инструкции по удалению базы данных см. в статье Удаление базы данных из Visual Studio 2012. Если вы используете этот подход, чтобы продолжить работу с этим руководством, пропустите шаг развертывания в конце этого руководства или выполните развертывание на новом сайте и в базе данных. Если вы развертываете обновление на том же сайте, на котором вы уже развертывали, EF получит такую же ошибку при автоматическом выполнении миграции. Если вы хотите устранить ошибку миграции, лучшим ресурсом является один из форумов entity Framework или StackOverflow.com.

Тестирование реализации

Запустите сайт и попробуйте различные страницы. Все работает так же, как и раньше.

В Обозреватель сервера разверните узел Подключения к данным\SchoolContext, а затем Таблицы, и вы увидите, что таблицы Student и Instructor были заменены таблицей Person. Разверните таблицу Person , и вы увидите, что в ней есть все столбцы, которые раньше были в таблицах Учащиеся и Преподаватели .

Щелкните таблицу Person правой кнопкой мыши и выберите команду Показать данные таблицы, чтобы просмотреть столбец дискриминатора.

На следующей схеме показана структура новой базы данных School.

School_database_diagram

Развернуть в Azure

В этом разделе необходимо выполнить необязательное развертывание приложения в Azure в части 3, Сортировка, фильтрация и разбиение по страницам этой серии учебников. Если у вас были ошибки миграции, которые были устранены путем удаления базы данных в локальном проекте, пропустите этот шаг. или создайте новый сайт и базу данных и выполните развертывание в новой среде.

  1. В Visual Studio щелкните правой кнопкой мыши проект в обозревателе решений и выберите Опубликовать в контекстном меню.

  2. Нажмите кнопку Опубликовать.

    Веб-приложение откроется в браузере по умолчанию.

  3. Протестируйте приложение, чтобы убедиться, что оно работает.

    При первом запуске страницы, которая обращается к базе данных, Entity Framework выполняет все методы миграции, необходимые Up для обновления базы данных с текущей моделью данных.

Получите код

Скачать завершенный проект

Дополнительные ресурсы

Ссылки на другие ресурсы Entity Framework можно найти в ASP.NET доступ к данным — рекомендуемые ресурсы.

Дополнительные сведения об этой и других структурах наследования см. в разделах Шаблон наследования TPT и Шаблон наследования TPH на сайте MSDN. В рамках следующего учебника вы узнаете, как работать в нескольких сценариях Entity Framework с расширенными возможностями.

Дальнейшие действия

Изучив это руководство, вы:

  • Научились сопоставлять наследование с базой данных
  • Создание класса Person
  • Обновление Instructor и Student
  • Добавлен человек в модель
  • Созданные и обновленные миграции
  • Тестирование реализации
  • Развертывание в Azure

Перейдите к следующей статье, чтобы узнать о темах, которые полезно учитывать, когда вы выходите за рамки основ разработки ASP.NET веб-приложений, использующих Entity Framework Code First.