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é:
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:
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.
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.
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
, Student
a 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 DbContext
a 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.cs
tří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.
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.
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.