Esercitazione: Implementare l'ereditarietà con Entity Framework in un'app MVC 5 ASP.NET
Nell'esercitazione precedente sono state gestite le eccezioni di concorrenza. In questa esercitazione viene illustrato come implementare l'ereditarietà nel modello di dati.
Nella programmazione orientata agli oggetti è possibile usare l'ereditarietà per facilitare il riutilizzo del codice. In questa esercitazione verranno modificate le classi Instructor
e Student
in modo che derivino da una classe di base Person
contenente proprietà quali LastName
comuni a docenti e studenti. Non verranno aggiunte o modificate pagine Web, ma si modificherà parte del codice e le modifiche verranno automaticamente riflesse nel database.
In questa esercitazione:
- Informazioni su come eseguire il mapping dell'ereditarietà al database
- Creare la classe Person
- Aggiornare Student e Instructor
- Aggiungere person al modello
- Creare e aggiornare migrazioni
- Testare l'implementazione
- Distribuisci in Azure
Prerequisiti
Eseguire il mapping dell'ereditarietà al database
Le Instructor
classi e Student
nel School
modello di dati hanno diverse proprietà identiche:
Si supponga di voler eliminare il codice ridondante per le proprietà condivise dalle entità Instructor
e Student
. Oppure che si desideri scrivere un servizio in grado di formattare i nomi senza sapere se il nome appartiene a un docente o a uno studente. È possibile creare una Person
classe di base contenente solo le proprietà condivise, quindi impostare le Instructor
entità e Student
ereditano da tale classe di base, come illustrato nella figura seguente:
Questa struttura di ereditarietà può essere rappresentata nel database in diversi modi. È possibile avere una Person
tabella che include informazioni su studenti e insegnanti in un'unica tabella. Alcune colonne possono essere applicate solo agli insegnanti (HireDate
), alcuni solo agli studenti (EnrollmentDate
), alcuni a entrambi (LastName
, FirstName
). In genere, è disponibile una colonna discriminatoria per indicare il tipo rappresentato da ogni riga. La colonna discriminante può ad esempio indicare "Instructor" per i docenti e "Student" per gli studenti.
Questo modello di generazione di una struttura di ereditarietà delle entità da una singola tabella di database è detta ereditarietà di tabella per gerarchia (TPH).
Un'alternativa consiste nel rendere il database più simile alla struttura di ereditarietà. Ad esempio, è possibile avere solo i campi del nome nella Person
tabella e avere tabelle separate Instructor
e Student
con i campi data.
Questo modello di creazione di una tabella di database per ogni classe di entità viene chiamato ereditarietà di tabella per tipo (TPT).
Un'altra opzione consiste nell'eseguire il mapping di tutti i tipi non astratti a singole tabelle. Tutte le proprietà di una classe, incluse le proprietà ereditate, eseguono il mapping alle colonne della tabella corrispondente. Questo criterio è denominato ereditarietà della classe tabella per tipo concreto. Se è stata implementata l'ereditarietà TPC per le Person
classi , Student
e Instructor
come illustrato in precedenza, le Student
tabelle e Instructor
non avranno un aspetto diverso dopo l'implementazione dell'ereditarietà rispetto a quelle precedenti.
I modelli di ereditarietà TPC e TPH offrono in genere prestazioni migliori in Entity Framework rispetto ai modelli di ereditarietà TPT, perché i modelli TPT possono comportare query di join complesse.
Questa esercitazione illustra come implementare l'ereditarietà tabella per gerarchia. TPH è il modello di ereditarietà predefinito in Entity Framework, quindi è necessario creare una Person
classe, modificare le Instructor
classi e Student
per derivare da Person
, aggiungere la nuova classe a DbContext
e creare una migrazione. Per informazioni su come implementare gli altri modelli di ereditarietà, vedere Mapping dell'ereditarietà di tabella per tipo (TPT) e mapping dell'ereditarietà della classe Table-Per-Concrete (TPC) nella documentazione di MSDN Entity Framework.
Creare la classe Person
Nella cartella Models creare Person.cs e sostituire il codice del modello con il codice seguente:
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;
}
}
}
}
Aggiornare Student e Instructor
Aggiornare ora Instructor.cs e Student.cs per ereditare i valori dal Person.sc.
In Instructor.cs derivare la Instructor
classe dalla Person
classe e rimuovere i campi chiave e nome. Il codice sarà simile all'esempio seguente:
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 virtual ICollection<Course> Courses { get; set; }
public virtual OfficeAssignment OfficeAssignment { get; set; }
}
}
Apportare modifiche simili a Student.cs. La Student
classe sarà simile all'esempio seguente:
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 virtual ICollection<Enrollment> Enrollments { get; set; }
}
}
Aggiungere person al modello
In SchoolContext.cs aggiungere una DbSet
proprietà per il Person
tipo di entità:
public DbSet<Person> People { get; set; }
L'ereditarietà tabella per gerarchia in Entity Framework è stata configurata. Come si noterà, quando il database viene aggiornato, avrà una Person
tabella al posto delle Student
tabelle e Instructor
.
Creare e aggiornare migrazioni
Nella console di Gestione pacchetti immettere il comando seguente:
Add-Migration Inheritance
Eseguire il Update-Database
comando in PMC. Il comando avrà esito negativo a questo punto perché sono presenti dati esistenti che le migrazioni non sanno come gestire. Viene visualizzato un messaggio di errore simile al seguente:
Impossibile eliminare l'oggetto 'dbo. Instructor' perché fa riferimento a un vincolo FOREIGN KEY.
Aprire Migrazioni< timestamp>_Inheritance.cs e sostituire il Up
metodo con il codice seguente:
public override void Up()
{
// Drop foreign keys and indexes that point to tables we're going to drop.
DropForeignKey("dbo.Enrollment", "StudentID", "dbo.Student");
DropIndex("dbo.Enrollment", new[] { "StudentID" });
RenameTable(name: "dbo.Instructor", newName: "Person");
AddColumn("dbo.Person", "EnrollmentDate", c => c.DateTime());
AddColumn("dbo.Person", "Discriminator", c => c.String(nullable: false, maxLength: 128, defaultValue: "Instructor"));
AlterColumn("dbo.Person", "HireDate", c => c.DateTime());
AddColumn("dbo.Person", "OldId", c => c.Int(nullable: true));
// Copy existing Student data into new Person table.
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.
Sql("UPDATE dbo.Enrollment SET StudentId = (SELECT ID FROM dbo.Person WHERE OldId = Enrollment.StudentId AND Discriminator = 'Student')");
// Remove temporary key
DropColumn("dbo.Person", "OldId");
DropTable("dbo.Student");
// Re-create foreign keys and indexes pointing to new table.
AddForeignKey("dbo.Enrollment", "StudentID", "dbo.Person", "ID", cascadeDelete: true);
CreateIndex("dbo.Enrollment", "StudentID");
}
Questo codice esegue le attività di aggiornamento del database seguenti:
Rimuove i vincoli di chiave esterna e gli indici che puntano alla tabella Student.
Rinomina la tabella Instructor in Person e apporta le modifiche necessarie perché sia in grado di archiviare i dati Student:
- Aggiunge un elemento EnrollmentDate che ammette i valori Null per gli studenti.
- Aggiunge una colonna discriminante che indica se una riga è per uno studente o un docente.
- Modifica HireDate in modo che ammetta i valori Null poiché le righe degli studenti non includono date di assunzione.
- Aggiunge un campo temporaneo che sarà usato per aggiornare le chiavi esterne che puntano agli studenti. Quando si copiano gli studenti nella tabella Person, otterranno nuovi valori di chiave primaria.
Copia i dati dalla tabella Student alla tabella Person. Ciò comporta l'assegnazione di nuovi valori di chiave primaria agli studenti.
Corregge i valori di chiave esterna che puntano agli studenti.
Ricrea i vincoli di chiave esterna e gli indici che ora puntano alla tabella Person.
Se come tipo di chiave primaria fosse stato usato GUID anziché Integer, non sarebbe necessario modificare i valori di chiave primaria degli studenti e molti di questi passaggi avrebbero potuto essere omessi.
Eseguire di nuovo il comando update-database
.
In un sistema di produzione è necessario apportare le modifiche corrispondenti al metodo Down nel caso in cui sia mai stato necessario usarlo per tornare alla versione precedente del database. Per questa esercitazione non si usa il metodo Down.
Nota
È possibile ottenere altri errori durante la migrazione dei dati e apportare modifiche allo schema. Se si verificano errori di migrazione non risolti, è possibile continuare con l'esercitazione modificando il stringa di connessione nel file diWeb.config o eliminando il database. L'approccio più semplice consiste nel rinominare il database nel file Web.config . Ad esempio, modificare il nome del database in ContosoUniversity2 come illustrato nell'esempio seguente:
<add name="SchoolContext"
connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=ContosoUniversity2;Integrated Security=SSPI;"
providerName="System.Data.SqlClient" />
Con un nuovo database, non sono presenti dati di cui eseguire la migrazione e il update-database
comando è molto più probabile che venga completato senza errori. Per istruzioni su come eliminare il database, vedere Come eliminare un database da Visual Studio 2012. Se si usa questo approccio per continuare con l'esercitazione, ignorare il passaggio di distribuzione alla fine di questa esercitazione o distribuirlo in un nuovo sito e database. Se si distribuisce un aggiornamento nello stesso sito in cui è già stata eseguita la distribuzione, Ef riceverà lo stesso errore quando esegue automaticamente le migrazioni. Se si vuole risolvere un errore di migrazione, la risorsa migliore è uno dei forum di Entity Framework o StackOverflow.com.
Testare l'implementazione
Eseguire il sito e provare varie pagine. Tutto funziona come in precedenza.
In Esplora server espandere Connessioni dati\SchoolContext e quindi Tabelle e osservare che le tabelle Student e Instructor sono state sostituite da una tabella Person . Espandere la tabella Person e si noterà che contiene tutte le colonne usate per essere presenti nelle tabelle Student e Instructor .
Fare clic con il pulsante destro del mouse sulla tabella Person e quindi su Mostra dati tabella per vedere la colonna discriminante.
Il diagramma seguente illustra la struttura del nuovo database School:
Distribuisci in Azure
Questa sezione richiede di aver completato la sezione facoltativa Distribuzione dell'app in Azure nella parte 3, Ordinamento, filtro e paging di questa serie di esercitazioni. Se si sono verificati errori di migrazione risolti eliminando il database nel progetto locale, ignorare questo passaggio; oppure creare un nuovo sito e un nuovo database e distribuirlo nel nuovo ambiente.
In Visual Studio fare clic con il pulsante destro del mouse sul progetto in Esplora soluzioni e scegliere Pubblica dal menu di scelta rapida.
Fare clic su Pubblica.
L'app Web viene aperta nel browser predefinito.
Testare l'applicazione per verificare che funzioni.
La prima volta che si esegue una pagina che accede al database, Entity Framework esegue tutti i metodi di migrazione
Up
necessari per aggiornare il database con il modello di dati corrente.
Ottenere il codice
Scaricare il progetto completato
Risorse aggiuntive
I collegamenti ad altre risorse di Entity Framework sono disponibili in ASP.NET Accesso ai dati - Risorse consigliate.
Per altre informazioni su questa e altre strutture di ereditarietà, vedere Modello di ereditarietà TPT e modello di ereditarietà TPH su MSDN. Nella prossima esercitazione si apprenderà come gestire diversi scenari Entity Framework relativamente avanzati.
Passaggi successivi
In questa esercitazione:
- Si è appreso come eseguire il mapping dell'ereditarietà al database
- Creare la classe Person
- Aggiornare Student e Instructor
- Aggiunta di Person al modello
- Migrazioni create e aggiornate
- Testare l'implementazione
- Distribuito in Azure
Passare all'articolo successivo per informazioni sugli argomenti utili per essere consapevoli di quando si superano le nozioni di base dello sviluppo di ASP.NET applicazioni Web che usano Entity Framework Code First.