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:
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:
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.
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 Instructor
Student
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.
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 Person
klas , Student
i, jak pokazano wcześniej, Student
tabele i Instructor
Instructor
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 Person
klasy , dodaj nową klasę do DbContext
klasy 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.cs
pliku 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.
Kliknij prawym przyciskiem myszy tabelę Person ,a następnie kliknij polecenie Pokaż dane tabeli, aby wyświetlić kolumnę dyskryminującą.
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.