Kurz: Implementace dědičnosti – ASP.NET MVC s EF Core

V předchozím kurzu jste zvládli výjimky souběžnosti. V tomto kurzu se dozvíte, jak implementovat dědičnost v datovém modelu.

V objektově orientovaném programování můžete pomocí dědičnosti usnadnit opakované použití kódu. V tomto kurzu změníte Instructor a Student třídy tak, aby byly odvozeny od Person základní třídy, která obsahuje vlastnosti, jako LastName jsou společné pro instruktory i studenty. Nebudete přidávat ani měnit žádné webové stránky, ale změníte kód a tyto změny se automaticky projeví v databázi.

V tomto kurzu se naučíte:

  • Mapování dědičnosti na databázi
  • Vytvoření třídy Person
  • Aktualizace instruktora a studenta
  • Přidání osoby do modelu
  • Vytváření a aktualizace migrací
  • Otestování implementace

Požadavky

Mapování dědičnosti na databázi

Student Datové Instructor modely a třídy ve školním datovém modelu mají několik vlastností, které jsou identické:

Student and Instructor classes

Předpokládejme, že chcete eliminovat redundantní kód pro vlastnosti sdílené entitami Instructor a Student entitami. Nebo chcete napsat službu, která může formátovat jména bez ohledu na to, jestli jméno pochází od instruktora nebo studenta. Mohli byste vytvořit Person základní třídu, která obsahuje pouze tyto sdílené vlastnosti, a pak zdědit Instructor třídy Student z této základní třídy, jak je znázorněno na následujícím obrázku:

Student and Instructor classes deriving from Person class

Tato struktura dědičnosti může být v databázi reprezentována několika způsoby. Můžete mít Person tabulku, která obsahuje informace o studentech i instruktorech v jedné tabulce. Některé sloupce můžou platit jenom pro instruktory (HireDate), některé jenom pro studenty (EnrollmentDate), některé pro oba (LastName, FirstName). Obvykle byste měli diskriminující sloupec, který označuje, který typ každého řádku představuje. Například diskriminující sloupec může mít pro instruktory "Instruktor" a "Student" pro studenty.

Table-per-hierarchy example

Tento model generování struktury dědičnosti entit z jedné databázové tabulky se nazývá dědičnost TPH (table-per-hierarchy).

Alternativou je, aby databáze vypadala spíše jako struktura dědičnosti. Můžete mít například jenom pole názvů v Person tabulce a mít samostatné Instructor a Student tabulky s poli kalendářních dat.

Upozorňující

Rozhraní TPT (Table-Per-Type) nepodporuje EF Core 3.x, ale je implementováno ve EF Core verzi 5.0.

Table-per-type inheritance

Tento model vytvoření databázové tabulky pro každou třídu entity se nazývá dědičnost typu tabulky (TPT).

Další možností je mapování všech ne abstraktních typů na jednotlivé tabulky. Všechny vlastnosti třídy, včetně zděděných vlastností, se mapují na sloupce odpovídající tabulky. Tento model se nazývá dědičnost třídy TPC (Table-per-Beton). Pokud jste implementovali dědičnost TPC pro Person, Studenta Instructor třídy, jak je uvedeno výše, Student a Instructor tabulky by po implementaci dědičnosti, než byly předtím.

Vzorce dědičnosti TPC a TPH obecně poskytují lepší výkon než vzory dědičnosti TPT, protože vzorce TPT můžou vést ke složitým dotazům spojení.

Tento kurz ukazuje, jak implementovat dědičnost TPH. TPH je jediný vzor dědičnosti, který Entity Framework Core podporuje. Uděláte to tak Person , že vytvoříte třídu, změníte Instructor a Student třídy odvozujete Person, přidáte novou třídu do objektu DbContexta vytvoříte migraci.

Tip

Před provedením následujících změn zvažte uložení kopie projektu. Pokud pak narazíte na problémy a potřebujete začít znovu, bude jednodušší začít od uloženého projektu namísto vrácení kroků provedených v tomto kurzu nebo návratu na začátek celé série.

Vytvoření třídy Person

Ve složce Models vytvořte Person.cs a nahraďte kód šablony následujícím kódem:

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

Aktualizace instruktora a studenta

Odvozujte Instructor.cstřídu instruktora z třídy Person a odeberte pole klíče a jména. Kód bude vypadat jako v následujícím příkladu:

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

Proveďte stejné změny v souboru 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; }
    }
}

Přidání osoby do modelu

Přidání typu entity Person do SchoolContext.cs. Nové řádky jsou zvýrazněné.

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

To je vše, co Entity Framework potřebuje ke konfiguraci dědičnosti tabulek na hierarchii. Jak uvidíte, když se databáze aktualizuje, bude mít místo tabulek Student a Instruktor tabulku Osoba.

Vytváření a aktualizace migrací

Uložte změny a sestavte projekt. Pak otevřete příkazové okno ve složce projektu a zadejte následující příkaz:

dotnet ef migrations add Inheritance

Zatím příkaz nespustíte database update . Výsledkem tohoto příkazu budou ztracená data, protože vypustí tabulku Instruktor a přejmenuje tabulku Student na Person. Abyste zachovali existující data, musíte zadat vlastní kód.

Otevřete Migrations/<timestamp>_Inheritance.cs a nahraďte metodu Up následujícím kódem:

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

Tento kód se postará o následující úlohy aktualizace databáze:

  • Odebere omezení cizího klíče a indexy, které odkazují na tabulku Student.

  • Přejmenuje tabulku Instruktor jako osobu a provede změny potřebné k ukládání dat studentů:

  • Přidá hodnotu nullable EnrollmentDate pro studenty.

  • Přidá diskriminující sloupec, který označuje, jestli je řádek určený pro studenta nebo instruktora.

  • Umožňuje Funkci HireDate null, protože řádky studentů nebudou mít data přijetí.

  • Přidá dočasné pole, které se použije k aktualizaci cizích klíčů, které odkazují na studenty. Když studenty zkopírujete do tabulky Person, získají nové hodnoty primárního klíče.

  • Zkopíruje data z tabulky Student do tabulky Osoba. Studenti tak budou mít přiřazené nové hodnoty primárního klíče.

  • Opravuje hodnoty cizího klíče, které odkazují na studenty.

  • Znovu vytvoří omezení a indexy cizího klíče, které teď odkazují na tabulku Person.

(Pokud jste jako typ primárního klíče použili identifikátor GUID místo celočíselného čísla, hodnoty primárního klíče studenta by se nemusely měnit a některé z těchto kroků by mohly být vynechány.)

database update Spusťte příkaz:

dotnet ef database update

(V produkčním systému byste provedli odpovídající změny metody Down v případě, že byste někdy museli tuto metodu použít, abyste se vrátili k předchozí verzi databáze. Pro účely tohoto kurzu nebudete tuto metodu Down používat.)

Poznámka

Při provádění změn schématu v databázi s existujícími daty je možné získat další chyby. Pokud dojde k chybám migrace, které nemůžete vyřešit, můžete změnit název databáze v připojovací řetězec nebo databázi odstranit. U nové databáze nejsou k dispozici žádná data k migraci a příkaz update-database bude pravděpodobně dokončen bez chyb. Pokud chcete databázi odstranit, použijte SSOX nebo spusťte příkaz rozhraní příkazového database drop řádku.

Otestování implementace

Spusťte aplikaci a vyzkoušejte různé stránky. Všechno funguje stejně jako předtím.

V SQL Serveru Průzkumník objektů rozbalte položku Data Připojení ions/SchoolContext a potom Tabulky a uvidíte, že tabulky Student a Instruktor byly nahrazeny tabulkou Osob. Otevřete návrháře tabulky Person a uvidíte, že obsahuje všechny sloupce, které se používaly v tabulkách Student a Instruktor.

Person table in SSOX

Klikněte pravým tlačítkem myši na tabulku Person (Osoba) a potom klepněte na příkaz Zobrazit data tabulky a zobrazte tak nediskriminační sloupec.

Person table in SSOX - table data

Získání kódu

Stáhněte nebo zobrazte dokončenou aplikaci.

Další prostředky

Další informace o dědičnosti v Entity Framework Core naleznete v tématu Dědičnost.

Další kroky

V tomto kurzu se naučíte:

  • Mapování dědičnosti na databázi
  • Vytvoření třídy Person
  • Aktualizace instruktora a studenta
  • Přidání osoby do modelu
  • Vytvoření a aktualizace migrací
  • Otestovali implementaci.

V dalším kurzu se dozvíte, jak zvládnout celou řadu relativně pokročilých scénářů Entity Frameworku.