Kurz: Implementace dědičnosti – ASP.NET MVC sEF 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, které 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řte třídu Person
  • Aktualizovat instruktora a studenta
  • Přidat osobu do modelu
  • Vytvořit a aktualizovat migrace
  • Otestujte implementaci

Prerequisites

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

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

Třídy studentů a instruktorů

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 nechat třídy Instructor a Student dědit z této základní třídy, jak je znázorněno na následujícím obrázku:

Třídy Student a Instructor odvozené od třídy Person

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 vyučující (HireDate), některé jenom pro studenty (EnrollmentDate), některé pro oba (LastName, FirstName). Obvykle byste měli rozlišovací sloupec, který určuje, jaký typ daný řádek představuje. Například diskriminující sloupec může mít pro instruktory "Instruktor" a "Student" pro studenty.

Příklad Table-per-Hierarchy

Tento model generování struktury dědičnosti entit z jediné 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.

Warning

Tabulka –Per-Type (TPT) není podporována EF Core verzí 3.x, ale byla implementována ve EF Core verzi 5.0.

Dědičnost typu po tabulkách

Tento vzor vytváření databázové tabulky pro každou třídu entity se nazývá dědičnost table-per-type (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 typu Table-per-Concrete Class (TPC). Pokud jste implementovali dědičnost TPC pro třídy Person, Student a Instructor, jak je uvedeno výše, tabulky Student a Instructor by po implementaci dědičnosti nevypadaly jinak než 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 využívajícím spojení tabulek.

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řte třídu 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;
            }
        }
    }
}

Aktualizovat instruktora a studenta

V Instructor.cs odvoďte třídu Instructor od třídy Person a odeberte pole key a name. 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řidat osobu 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 potřebuje Entity Framework ke konfiguraci dědičnosti typu tabulka-na-hierarchii. Jak uvidíte, když se databáze aktualizuje, bude mít místo tabulek Student a Instruktor tabulku Osoba.

Vytvořit a aktualizovat migrace

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 nespouštějte příkaz 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 Instructor na Person a provede změny potřebné k ukládání dat Student:

  • Přidá pole EnrollmentDate s povolenou hodnotou null pro studenty.

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

  • Nastaví, aby HireDate mohlo být 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 Person. Studentům tak budou přiřazeny nové hodnoty primárního klíče.

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

  • Znovu vytvoří omezení cizího klíče a indexy, 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.)

Spusťte příkaz database update:

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.)

Note

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 žá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 database drop příkaz rozhraní CLI.

Otestujte implementaci

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

V SQL Server Průzkumník objektů rozbalte Datová připojení/SchoolContext a potom Tabulky a uvidíte, že tabulky Student a Instructor byly nahrazeny tabulkou Person. Otevřete návrháře tabulky Person a uvidíte, že obsahuje všechny sloupce, které dříve byly v tabulkách Student a Instructor.

Tabulka osob v SSOX

Klikněte pravým tlačítkem myši na tabulku Person (Osoba) a potom klikněte na Zobrazit data tabulky a zobrazte tak sloupec diskriminátoru.

Tabulka osob v SSOX – data tabulky

Získání kódu

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

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:

  • Namapovaná dědičnost do databáze
  • Vytvořena třída Person
  • Aktualizovaný lektor a student
  • Osoba byla přidána do modelu
  • Vytvořte a aktualizujte migrace
  • Testována implementace

Pokračujte k dalšímu návodu a dozvíte se, jak zvládnout celou řadu relativně pokročilých scénářů Entity Frameworku.