Samouczek: implementowanie dziedziczenia — ASP.NET MVC za pomocą polecenia EF Core

W poprzednim samouczku obsłużyliśmy wyjątki współbieżności. W tym samouczku pokazano, jak zaimplementować dziedziczenie w modelu danych.

W programowaniu obiektowym można użyć dziedziczenia w celu ułatwienia ponownego użycia kodu. W tym samouczku zmienisz Instructor klasy i Student tak, aby pochodziły one z klasy bazowej Person , która zawiera właściwości, takie jak LastName wspólne dla instruktorów i studentów. Nie dodasz ani nie zmienisz żadnych stron internetowych, ale zmienisz część kodu, a te zmiany zostaną automatycznie odzwierciedlone w bazie danych.

W tym samouczku zostały wykonane następujące czynności:

  • Mapuj dziedziczenie na bazę danych
  • Tworzenie klasy Person
  • Aktualizowanie instruktora i ucznia
  • Dodawanie osoby do modelu
  • Tworzenie i aktualizowanie migracji
  • Testowanie implementacji

Wymagania wstępne

Mapuj dziedziczenie na bazę danych

Klasy Instructor i Student w modelu danych szkoły mają kilka właściwości, które są identyczne:

Student and Instructor classes

Załóżmy, że chcesz wyeliminować nadmiarowy kod właściwości, które są współużytkowane przez Instructor jednostki i Student . Możesz też napisać usługę, która może formatować nazwy bez dbania o to, czy nazwa pochodzi od instruktora, czy studenta. Można utworzyć klasę bazową zawierającą Person tylko te właściwości udostępnione, a następnie ustawić Instructor klasy i Student dziedziczą po tej klasie bazowej, jak pokazano na poniższej ilustracji:

Student and Instructor classes deriving from Person class

Istnieje kilka sposobów reprezentowania tej struktury dziedziczenia w bazie danych. Możesz mieć tabelę zawierającą Person informacje o uczniach i instruktorach w jednej tabeli. Niektóre kolumny mogą dotyczyć tylko instruktorów (HireDate), a niektóre tylko dla uczniów (EnrollmentDate), niektóre do obu (LastName, FirstName). Zazwyczaj istnieje kolumna dyskryminująca wskazująca typ reprezentowany przez poszczególne wiersze. Na przykład kolumna dyskryminująca może mieć wartość "Instruktor" dla instruktorów i "Student" dla uczniów.

Table-per-hierarchy example

Ten wzorzec generowania struktury dziedziczenia jednostki z pojedynczej tabeli bazy danych jest nazywany dziedziczeniem tabeli na hierarchię (TPH).

Alternatywą jest uczynienie bazy danych bardziej podobną do struktury dziedziczenia. Na przykład można mieć tylko pola nazw w Person tabeli i mieć oddzielne InstructorStudent i tabele z polami daty.

Ostrzeżenie

Typ tabeli (TPT) nie jest obsługiwany przez EF Core 3.x, ale został zaimplementowany w wersji EF Core 5.0.

Table-per-type inheritance

Ten wzorzec tworzenia tabeli bazy danych dla każdej klasy jednostek jest nazywany dziedziczeniem tabeli na typ (TPT).

Kolejną opcją jest mapowania wszystkich typów nie abstrakcyjnych na poszczególne tabele. Wszystkie właściwości klasy, w tym właściwości dziedziczone, są mapowane na kolumny odpowiedniej tabeli. Ten wzorzec jest nazywany dziedziczeniem klasy TPC (Table-per-Concrete Class). Jeśli zaimplementowano dziedziczenie TPC dla Personklas , Studenti, jak pokazano wcześniej, Student tabele i InstructorInstructor nie będą wyglądać inaczej po zaimplementowaniu dziedziczenia niż wcześniej.

Wzorce dziedziczenia TPC i TPH zwykle zapewniają lepszą wydajność niż wzorce dziedziczenia TPT, ponieważ wzorce TPT mogą powodować złożone zapytania sprzężenia.

W tym samouczku pokazano, jak zaimplementować dziedziczenie TPH. TPH to jedyny wzorzec dziedziczenia, który obsługuje program Entity Framework Core. W tym celu utworzysz klasę Person , zmień Instructor klasy i Student , aby pochodziły z Personklasy , dodaj nową klasę do DbContextklasy i utwórz migrację.

Napiwek

Przed wprowadzeniem poniższych zmian rozważ zapisanie kopii projektu. Jeśli wystąpią problemy i trzeba zacząć od nowa, łatwiej będzie rozpocząć od zapisanego projektu zamiast odwracać kroki wykonywane dla tego samouczka lub wracać do początku całej serii.

Tworzenie klasy Person

W folderze Models utwórz plik Person.cs i zastąp kod szablonu następującym kodem:

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

Aktualizowanie instruktora i ucznia

W Instructor.cspliku utwórz klasę Instruktor z klasy Person i usuń pola klucza i nazwy. Kod będzie wyglądać podobnie do poniższego przykładu:

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 ICollection<CourseAssignment> CourseAssignments { get; set; }
        public OfficeAssignment OfficeAssignment { get; set; }
    }
}

Wprowadź te same zmiany w pliku 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 ICollection<Enrollment> Enrollments { get; set; }
    }
}

Dodawanie osoby do modelu

Dodaj typ jednostki Person do SchoolContext.cs. Nowe wiersze są wyróżnione.

using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;

namespace ContosoUniversity.Data
{
    public class SchoolContext : DbContext
    {
        public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
        {
        }

        public DbSet<Course> Courses { get; set; }
        public DbSet<Enrollment> Enrollments { get; set; }
        public DbSet<Student> Students { get; set; }
        public DbSet<Department> Departments { get; set; }
        public DbSet<Instructor> Instructors { get; set; }
        public DbSet<OfficeAssignment> OfficeAssignments { get; set; }
        public DbSet<CourseAssignment> CourseAssignments { get; set; }
        public DbSet<Person> People { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Course>().ToTable("Course");
            modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
            modelBuilder.Entity<Student>().ToTable("Student");
            modelBuilder.Entity<Department>().ToTable("Department");
            modelBuilder.Entity<Instructor>().ToTable("Instructor");
            modelBuilder.Entity<OfficeAssignment>().ToTable("OfficeAssignment");
            modelBuilder.Entity<CourseAssignment>().ToTable("CourseAssignment");
            modelBuilder.Entity<Person>().ToTable("Person");

            modelBuilder.Entity<CourseAssignment>()
                .HasKey(c => new { c.CourseID, c.InstructorID });
        }
    }
}

Jest to wszystko, co platforma Entity Framework potrzebuje w celu skonfigurowania dziedziczenia tabeli na hierarchię. Jak zobaczysz, po zaktualizowaniu bazy danych będzie ona zawierać tabelę Person zamiast tabel Student i Instruktor.

Tworzenie i aktualizowanie migracji

Zapisz zmiany i skompiluj projekt. Następnie otwórz okno polecenia w folderze projektu i wprowadź następujące polecenie:

dotnet ef migrations add Inheritance

Nie uruchamiaj database update jeszcze polecenia. To polecenie spowoduje utratę danych, ponieważ spowoduje to usunięcie tabeli Instruktor i zmianę nazwy tabeli Student na Person. Aby zachować istniejące dane, musisz podać kod niestandardowy.

Otwórz Migrations/<timestamp>_Inheritance.cs metodę i zastąp metodę Up następującym kodem:

protected override void Up(MigrationBuilder migrationBuilder)
{
    migrationBuilder.DropForeignKey(
        name: "FK_Enrollment_Student_StudentID",
        table: "Enrollment");

    migrationBuilder.DropIndex(name: "IX_Enrollment_StudentID", table: "Enrollment");

    migrationBuilder.RenameTable(name: "Instructor", newName: "Person");
    migrationBuilder.AddColumn<DateTime>(name: "EnrollmentDate", table: "Person", nullable: true);
    migrationBuilder.AddColumn<string>(name: "Discriminator", table: "Person", nullable: false, maxLength: 128, defaultValue: "Instructor");
    migrationBuilder.AlterColumn<DateTime>(name: "HireDate", table: "Person", nullable: true);
    migrationBuilder.AddColumn<int>(name: "OldId", table: "Person", nullable: true);

    // Copy existing Student data into new Person table.
    migrationBuilder.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.
    migrationBuilder.Sql("UPDATE dbo.Enrollment SET StudentId = (SELECT ID FROM dbo.Person WHERE OldId = Enrollment.StudentId AND Discriminator = 'Student')");

    // Remove temporary key
    migrationBuilder.DropColumn(name: "OldID", table: "Person");

    migrationBuilder.DropTable(
        name: "Student");

    migrationBuilder.CreateIndex(
         name: "IX_Enrollment_StudentID",
         table: "Enrollment",
         column: "StudentID");

    migrationBuilder.AddForeignKey(
        name: "FK_Enrollment_Person_StudentID",
        table: "Enrollment",
        column: "StudentID",
        principalTable: "Person",
        principalColumn: "ID",
        onDelete: ReferentialAction.Cascade);
}

Ten kod zajmuje się następującymi zadaniami aktualizacji bazy danych:

  • Usuwa ograniczenia i indeksy klucza obcego wskazujące tabelę Student.

  • Zmienia nazwę tabeli Instruktor jako Osoba i wprowadza zmiany wymagane do przechowywania danych uczniów:

  • Dodaje wartość nullable EnrollmentDate dla uczniów.

  • Dodaje kolumnę dyskryminującą, aby wskazać, czy wiersz jest przeznaczony dla ucznia, czy instruktora.

  • Sprawia, że funkcja HireDate może mieć wartość null, ponieważ wiersze uczniów nie będą miały dat zatrudnienia.

  • Dodaje pole tymczasowe, które będzie używane do aktualizowania kluczy obcych wskazujących uczniów. Podczas kopiowania uczniów do tabeli Person otrzymają nowe wartości klucza podstawowego.

  • Kopiuje dane z tabeli Student do tabeli Person. Powoduje to, że uczniowie otrzymują przypisane nowe wartości klucza podstawowego.

  • Naprawia obce wartości kluczy, które wskazują uczniów.

  • Ponownie tworzy ograniczenia i indeksy kluczy obcych, wskazując je teraz na tabelę Person.

(Jeśli użyto identyfikatora GUID zamiast liczby całkowitej jako typu klucza podstawowego, wartości klucza podstawowego ucznia nie musiałyby ulec zmianie, a kilka z tych kroków mogło zostać pominiętych).

database update Uruchom polecenie:

dotnet ef database update

(W systemie produkcyjnym należy wprowadzić odpowiednie zmiany Down w metodzie, jeśli kiedykolwiek trzeba było użyć tej metody, aby wrócić do poprzedniej wersji bazy danych. Na potrzeby tego samouczka Down nie będziesz używać metody .

Uwaga

Podczas wprowadzania zmian schematu w bazie danych z istniejącymi danymi można uzyskać inne błędy. Jeśli wystąpią błędy migracji, których nie można rozwiązać, możesz zmienić nazwę bazy danych w parametry połączenia lub usunąć bazę danych. W przypadku nowej bazy danych nie ma danych do zmigrowania, a polecenie update-database jest bardziej prawdopodobne, aby ukończyć bez błędów. Aby usunąć bazę danych, użyj programu SSOX lub uruchom polecenie interfejsu database drop wiersza polecenia.

Testowanie implementacji

Uruchom aplikację i wypróbuj różne strony. Wszystko działa tak samo jak wcześniej.

W Eksplorator obiektów programu SQL Server rozwiń węzeł Data Połączenie ions/SchoolContext, a następnie tabele Tabele uczniów i instruktorów zostały zastąpione przez tabelę Person. Otwórz projektanta tabeli Person i zobaczysz, że zawiera wszystkie kolumny, które były używane w tabelach Student i Instruktor.

Person table in SSOX

Kliknij prawym przyciskiem myszy tabelę Person ,a następnie kliknij polecenie Pokaż dane tabeli, aby wyświetlić kolumnę dyskryminującą.

Person table in SSOX - table data

Uzyskiwanie kodu

Pobierz lub wyświetl ukończoną aplikację.

Dodatkowe zasoby

Aby uzyskać więcej informacji na temat dziedziczenia w programie Entity Framework Core, zobacz Dziedziczenie.

Następne kroki

W tym samouczku zostały wykonane następujące czynności:

  • Zamapowane dziedziczenie do bazy danych
  • Utworzono klasę Person
  • Zaktualizowano instruktora i ucznia
  • Dodano osobę do modelu
  • Utworzone i zaktualizowane migracje
  • Przetestowano implementację

Przejdź do następnego samouczka, aby dowiedzieć się, jak obsługiwać różne stosunkowo zaawansowane scenariusze programu Entity Framework.