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 ze Person základní třídy, která obsahuje vlastnosti, jako jsou například vlastnosti, které LastName jsou společné pro instruktory i studenty. Nepřidáte ani nezměníte žádné webové stránky, ale změníte některý kód a tyto změny se automaticky projeví v databázi.

V tomto kurzu jste:

  • Mapování dědičnosti do databáze
  • 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 do databáze

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

Student and Instructor classes

Předpokládejme, že chcete odstranit redundantní kód pro vlastnosti, které jsou sdíleny 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. Můžete 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

Existuje několik způsobů, jak by tato struktura dědičnosti mohla být reprezentována v databázi. 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 (Příjmení, Jméno). 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 instruktora pro instruktory a studenta.

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 více 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.

Upozornění

Ef Core 3.x nepodporuje tabulku na typ (TPT), ale je implementovaná v EF Core 5.0.

Table-per-type inheritance

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

Další možností je namapovat všechny ne abstraktní typy na jednotlivé tabulky. Všechny vlastnosti třídy, včetně zděděných vlastností, mapují na sloupce odpovídající tabulky. Tento model se nazývá dědičnost třídy TPC (Table-per-Concrete). Pokud jste implementovali dědičnost TPC pro Person, Studenta Instructor třídy, jak je znázorněno výše, nebudou se Instructor po implementaci dědičnosti vypadat jinak než Student dříve.

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

Tento kurz ukazuje, jak implementovat dědičnost TPH. TPH je jediný model dědičnosti, který Entity Framework Core podporuje. Uděláte to Person tak, ž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, místo abyste se vrátili k krokům provedeným pro tento kurz nebo se vrátíte na začátek celé série.

Vytvoření třídy Person

Ve složce Modely 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 Student.cssouboru .

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řidejte typ 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

Příkaz zatím nespustíte database update . Výsledkem tohoto příkazu budou ztracená data, protože vypustí tabulku Instruktor a přejmenuje tabulku Student na Osobu. 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 osoba 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.

  • Nastaví funkci HireDate null, protože řádky studentů nebudou mít data pronájmu.

  • 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, budou mít nové hodnoty primárního klíče.

  • Zkopíruje data z tabulky Student do tabulky Osoba. To způsobí, že studenti 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 Osoba.

(Pokud jste místo celočíselného čísla použili identifikátor GUID jako typ primárního klíče, 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 Down metody v případě, že byste ji museli použít k návratu k předchozí verzi databáze. Pro účely tohoto kurzu nebudete tuto metodu Down používat.)

Poznámka

Při prováděnízměnch Pokud se zobrazí chyby migrace, které nemůžete vyřešit, můžete změnit název databáze v připojovacím řetězci nebo odstranit databázi. V nové databázi 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 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 Průzkumníku objektů SQL Serveru rozbalte datová připojení/ SchoolContext a pak tabulky a uvidíte, že tabulky studenta a instruktora nahradila tabulka Osoba. 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 Osoba a potom klikněte na Zobrazit data tabulky a zobrazte tak diskriminující sloupec.

Person table in SSOX - table data

Získání kódu

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

Další materiály

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

Další kroky

V tomto kurzu jste:

  • Mapovaná dědičnost na databázi
  • Vytvoření třídy Person
  • Aktualizace instruktora a studenta
  • Přidání osoby do modelu
  • Vytvář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.