Parte 6, Razor Pagine con EF Core in ASP.NET Core - Leggere i dati correlati
Di Tom Dykstra, Jon P Smith e Rick Anderson
L'app Web Contoso University illustra come creare Razor app Web Pages usando EF Core e Visual Studio. Per informazioni sulla serie di esercitazioni, vedere la prima esercitazione.
Se si verificano problemi che non è possibile risolvere, scaricare l'app completata e confrontare tale codice con quello creato seguendo questa esercitazione.
Questa esercitazione illustra come leggere e visualizzare dati correlati. I dati correlati sono dati EF Core caricati nelle proprietà di navigazione.
Le figure seguenti mostrano le pagine completate per questa esercitazione:
Caricamento eager, esplicito e lazy
Esistono diversi modi per EF Core caricare i dati correlati nelle proprietà di navigazione di un'entità:
Caricamento eager. Il caricamento eager si verifica quando una query per un solo tipo di entità carica anche entità correlate. Quando viene letta un'entità, vengono recuperati i dati correlati corrispondenti. Ciò in genere ha come risultato una query join singola che recupera tutti i dati necessari. EF Core eseguirà più query per alcuni tipi di caricamento eager. L'esecuzione di più query può essere più efficiente rispetto a una singola query di grandi dimensioni. Il caricamento eager viene specificato con i metodi Include e ThenInclude.
Il caricamento eager invia più query quando è inclusa una navigazione di raccolte:
- Una query per la query principale
- Una query per ogni raccolta "perimetrale" nell'albero del caricamento.
Query separate con
Load
: i dati possono essere recuperati in query separate e EF Core "corregge" le proprietà di navigazione. "Correzioni" indica che EF Core popola automaticamente le proprietà di navigazione. Le query separate conLoad
sono più simili a un caricamento esplicito che a un caricamento eager.Nota:EF Core corregge automaticamente le proprietà di navigazione in tutte le altre entità caricate in precedenza nell'istanza di contesto. Anche se i dati per una proprietà di navigazione non sono inclusi in modo esplicito, la proprietà può comunque essere popolata se alcune o tutte le entità correlate sono state caricate in precedenza.
Caricamento esplicito Quando un'entità viene letta per la prima volta, i dati correlati non vengono recuperati. Per recuperare i dati correlati quando necessario, è necessario scrivere codice. Il caricamento esplicito con query separate ha come risultato l'invio di più query al database. Con il caricamento esplicito, il codice specifica le proprietà di navigazione da caricare. Per eseguire il caricamento esplicito, usare il metodo
Load
. Ad esempio:Caricamento lazy. Quando un'entità viene letta per la prima volta, i dati correlati non vengono recuperati. La prima volta che si accede a una proprietà di navigazione, i dati necessari per quest'ultima vengono recuperati automaticamente. Ogni volta che si accede a una proprietà di navigazione per la prima volta, viene inviata una query al database. Il caricamento differita può compromettere le prestazioni, ad esempio quando gli sviluppatori usano query N+1. Le query N+1 caricano un elemento padre ed enumerare gli elementi figlio.
Creare pagine Course
L'entità Course
include una proprietà di navigazione che contiene l'entità Department
correlata.
Per visualizzare il nome del dipartimento assegnato per un corso:
- Caricare l'entità
Department
correlata nella proprietà di navigazioneCourse.Department
. - Ottenere il nome dalla proprietà
Name
dell'entitàDepartment
.
Scaffolding delle pagine Course
Seguire le istruzioni in Scaffolding delle pagine Student con le eccezioni seguenti:
- Creare una cartella Pages/Courses.
- Usare
Course
per la classe del modello. - Usare la classe di contesto esistente anziché crearne una nuova.
Aprire
Pages/Courses/Index.cshtml.cs
ed esaminare ilOnGetAsync
metodo . Il motore di scaffolding ha specificato il caricamento eager per la proprietà di navigazioneDepartment
. Il metodoInclude
specifica il caricamento eager.Eseguire l'app e selezionare il collegamento Courses (Corsi). La colonna dei dipartimenti visualizza il
DepartmentID
, che non è utile.
Visualizzare il nome del dipartimento
Aggiornare Pages/Courses/Index.cshtml.cs con il codice seguente:
using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace ContosoUniversity.Pages.Courses
{
public class IndexModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;
public IndexModel(ContosoUniversity.Data.SchoolContext context)
{
_context = context;
}
public IList<Course> Courses { get; set; }
public async Task OnGetAsync()
{
Courses = await _context.Courses
.Include(c => c.Department)
.AsNoTracking()
.ToListAsync();
}
}
}
Il codice precedente modifica la proprietà Course
in Courses
e aggiunge AsNoTracking
.
Le query senza rilevamento sono utili quando i risultati vengono usati in uno scenario di sola lettura. In genere sono più veloci da eseguire perché non è necessario configurare le informazioni sul rilevamento delle modifiche. Se non è necessario aggiornare le entità recuperate dal database, è probabile che una query di rilevamento non funzioni meglio di una query di rilevamento.
In alcuni casi una query di rilevamento è più efficiente di una query senza rilevamento. Per altre informazioni, vedere Tracking vs. No-Tracking Queries.For more information, see Tracking vs. No-Tracking Queries.
Nel codice precedente viene AsNoTracking
chiamato perché le entità non vengono aggiornate nel contesto corrente.
Aggiornare Pages/Courses/Index.cshtml
con il codice seguente.
@page
@model ContosoUniversity.Pages.Courses.IndexModel
@{
ViewData["Title"] = "Courses";
}
<h1>Courses</h1>
<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Courses[0].CourseID)
</th>
<th>
@Html.DisplayNameFor(model => model.Courses[0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Courses[0].Credits)
</th>
<th>
@Html.DisplayNameFor(model => model.Courses[0].Department)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Courses)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.CourseID)
</td>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.Credits)
</td>
<td>
@Html.DisplayFor(modelItem => item.Department.Name)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.CourseID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.CourseID">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.CourseID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Al codice con scaffolding sono state apportate le modifiche seguenti:
Il nome della proprietà
Course
è stato modificato inCourses
.È stata aggiunta la colonna Number (Numero) con il valore della proprietà
CourseID
. Per impostazione predefinita, non viene eseguito lo scaffolding delle chiavi primarie, perché in genere non sono significative per gli utenti finali. In questo caso, tuttavia, la chiave primaria è significativa.Modificare la colonna Department (Dipartimento) per visualizzare il nome del dipartimento. Il codice visualizza la proprietà
Name
dell'entitàDepartment
che viene caricata nella proprietà di navigazioneDepartment
:@Html.DisplayFor(modelItem => item.Department.Name)
Eseguire l'app e selezionare la scheda Courses (Corsi) per visualizzare l'elenco con i nomi dei dipartimenti.
Caricamento di dati correlati con Select
Il metodo OnGetAsync
carica i dati correlati con il metodo Include
. Il metodo Select
è un'alternativa che carica solo i dati correlati necessari. Per i singoli elementi, ad esempio , Department.Name
usa un oggetto SQL INNER JOIN
. Per le raccolte usa un altro accesso al database, ma anche l'operatore Include
fa lo stesso sulle raccolte.
Il codice seguente carica dati correlati con il metodo Select
:
public IList<CourseViewModel> CourseVM { get; set; }
public async Task OnGetAsync()
{
CourseVM = await _context.Courses
.Select(p => new CourseViewModel
{
CourseID = p.CourseID,
Title = p.Title,
Credits = p.Credits,
DepartmentName = p.Department.Name
}).ToListAsync();
}
Il codice precedente non restituisce alcun tipo di entità, pertanto non viene eseguito alcun rilevamento. Per altre informazioni sul rilevamento di Entity Framework, vedere Tracking vs. No-Tracking Queries.For more information about the EF tracking, see Tracking vs. No-Tracking Queries.
CourseViewModel
:
public class CourseViewModel
{
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public string DepartmentName { get; set; }
}
Per le pagine completeRazor, vedere IndexSelectModel.
Creare pagine Instructor
Questa sezione descrive come eseguire lo scaffolding delle pagine Instructor e come aggiungere corsi e iscrizioni correlati alla pagina di indice degli insegnanti.
Questa pagina legge e visualizza dati correlati nei modi seguenti:
- L'elenco di insegnanti visualizza dati correlati provenienti dall'entità
OfficeAssignment
, Office (Ufficio) nella figura precedente. Tra le entitàInstructor
eOfficeAssignment
c'è una relazione uno-a-zero-o-uno. Per le entitàOfficeAssignment
viene usato il caricamento eager. Il caricamento eager è in genere più efficiente quando i dati correlati devono essere visualizzati. In questo caso, devono essere visualizzate le assegnazioni di ufficio degli insegnanti. - Quando l'utente seleziona un insegnante, vengono visualizzate le entità
Course
correlate. Tra le entitàInstructor
eCourse
esiste una relazione molti-a-molti. Il caricamento eager viene usato per le entitàCourse
e le entitàDepartment
correlate. In questo caso, query separate potrebbero essere più efficienti, perché sono necessari solo i corsi per l'insegnante selezionato. Questo esempio illustra come usare il caricamento eager per le proprietà di navigazione di entità all'interno di proprietà di navigazione. - Quando l'utente seleziona un corso, vengono visualizzati i dati correlati dall'entità
Enrollments
. Nella figura precedente, vengono visualizzati il voto e il nome degli studenti. Tra le entitàCourse
eEnrollment
esiste una relazione uno-a-molti.
Creare un modello di visualizzazione
La pagina Instructors (Insegnanti) mostra i dati di tre tabelle diverse. È necessario un modello di visualizzazione che includa tre proprietà che rappresentano le tre tabelle.
Creare Models/SchoolViewModels/InstructorIndexData.cs
con il codice seguente:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ContosoUniversity.Models.SchoolViewModels
{
public class InstructorIndexData
{
public IEnumerable<Instructor> Instructors { get; set; }
public IEnumerable<Course> Courses { get; set; }
public IEnumerable<Enrollment> Enrollments { get; set; }
}
}
Scaffolding delle pagine Instructor
Seguire le istruzioni in Scaffolding delle pagine Student con le eccezioni seguenti:
- Creare una cartella Pages/Instructors.
- Usare
Instructor
per la classe del modello. - Usare la classe di contesto esistente anziché crearne una nuova.
Eseguire l'app e passare alla pagina Instructors (Insegnanti).
Aggiornare Pages/Instructors/Index.cshtml.cs
con il codice seguente:
using ContosoUniversity.Models;
using ContosoUniversity.Models.SchoolViewModels; // Add VM
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ContosoUniversity.Pages.Instructors
{
public class IndexModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;
public IndexModel(ContosoUniversity.Data.SchoolContext context)
{
_context = context;
}
public InstructorIndexData InstructorData { get; set; }
public int InstructorID { get; set; }
public int CourseID { get; set; }
public async Task OnGetAsync(int? id, int? courseID)
{
InstructorData = new InstructorIndexData();
InstructorData.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.Courses)
.ThenInclude(c => c.Department)
.OrderBy(i => i.LastName)
.ToListAsync();
if (id != null)
{
InstructorID = id.Value;
Instructor instructor = InstructorData.Instructors
.Where(i => i.ID == id.Value).Single();
InstructorData.Courses = instructor.Courses;
}
if (courseID != null)
{
CourseID = courseID.Value;
IEnumerable<Enrollment> Enrollments = await _context.Enrollments
.Where(x => x.CourseID == CourseID)
.Include(i=>i.Student)
.ToListAsync();
InstructorData.Enrollments = Enrollments;
}
}
}
}
Il metodo OnGetAsync
accetta i dati di route facoltativi per l'ID dell'insegnante selezionato.
Esaminare la query nel Pages/Instructors/Index.cshtml.cs
file:
InstructorData = new InstructorIndexData();
InstructorData.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.Courses)
.ThenInclude(c => c.Department)
.OrderBy(i => i.LastName)
.ToListAsync();
Il codice specifica il caricamento eager delle proprietà di navigazione seguenti:
Instructor.OfficeAssignment
Instructor.Courses
Course.Department
Il codice seguente viene eseguito quando viene selezionato un insegnante, id != null
ovvero .
if (id != null)
{
InstructorID = id.Value;
Instructor instructor = InstructorData.Instructors
.Where(i => i.ID == id.Value).Single();
InstructorData.Courses = instructor.Courses;
}
L'insegnante selezionato viene recuperato dall'elenco di insegnanti nel modello di visualizzazione. La proprietà del modello di Courses
visualizzazione viene caricata con le Course
entità dalla proprietà di navigazione dell'insegnante Courses
selezionato.
Il metodo Where
restituisce una raccolta. In questo caso, il filtro seleziona una singola entità, quindi viene chiamato il Single
metodo per convertire la raccolta in una singola Instructor
entità. L'entità Instructor
fornisce l'accesso alla proprietà di Course
navigazione.
Il metodo Single viene usato in una raccolta quando quest'ultima ha un solo elemento. Il metodo Single
genera un'eccezione se la raccolta è vuota o se contiene più elementi. Un'alternativa è SingleOrDefault, che restituisce un valore predefinito se la raccolta è vuota. Per questa query, null
nel valore predefinito restituito.
Il codice seguente popola la proprietà Enrollments
del modello di visualizzazione quando è selezionato un corso:
if (courseID != null)
{
CourseID = courseID.Value;
IEnumerable<Enrollment> Enrollments = await _context.Enrollments
.Where(x => x.CourseID == CourseID)
.Include(i=>i.Student)
.ToListAsync();
InstructorData.Enrollments = Enrollments;
}
Aggiornare la pagina di indice degli insegnanti
Aggiornare Pages/Instructors/Index.cshtml
con il codice seguente.
@page "{id:int?}"
@model ContosoUniversity.Pages.Instructors.IndexModel
@{
ViewData["Title"] = "Instructors";
}
<h2>Instructors</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>Last Name</th>
<th>First Name</th>
<th>Hire Date</th>
<th>Office</th>
<th>Courses</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.InstructorData.Instructors)
{
string selectedRow = "";
if (item.ID == Model.InstructorID)
{
selectedRow = "table-success";
}
<tr class="@selectedRow">
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.HireDate)
</td>
<td>
@if (item.OfficeAssignment != null)
{
@item.OfficeAssignment.Location
}
</td>
<td>
@{
foreach (var course in item.Courses)
{
@course.CourseID @: @course.Title <br />
}
}
</td>
<td>
<a asp-page="./Index" asp-route-id="@item.ID">Select</a> |
<a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
@if (Model.InstructorData.Courses != null)
{
<h3>Courses Taught by Selected Instructor</h3>
<table class="table">
<tr>
<th></th>
<th>Number</th>
<th>Title</th>
<th>Department</th>
</tr>
@foreach (var item in Model.InstructorData.Courses)
{
string selectedRow = "";
if (item.CourseID == Model.CourseID)
{
selectedRow = "table-success";
}
<tr class="@selectedRow">
<td>
<a asp-page="./Index" asp-route-courseID="@item.CourseID">Select</a>
</td>
<td>
@item.CourseID
</td>
<td>
@item.Title
</td>
<td>
@item.Department.Name
</td>
</tr>
}
</table>
}
@if (Model.InstructorData.Enrollments != null)
{
<h3>
Students Enrolled in Selected Course
</h3>
<table class="table">
<tr>
<th>Name</th>
<th>Grade</th>
</tr>
@foreach (var item in Model.InstructorData.Enrollments)
{
<tr>
<td>
@item.Student.FullName
</td>
<td>
@Html.DisplayFor(modelItem => item.Grade)
</td>
</tr>
}
</table>
}
Il codice precedente apporta le modifiche seguenti:
Aggiorna la
page
direttiva a@page "{id:int?}"
."{id:int?}"
è un modello di route. Il modello di route modifica le stringhe di query integer nell'URL per instradare i dati. Se ad esempio si fa clic su sul collegamento Select (Seleziona) per un insegnante con la sola direttiva@page
, viene generato un URL come il seguente:https://localhost:5001/Instructors?id=2
Quando la direttiva di pagina è
@page "{id:int?}"
, l'URL è:https://localhost:5001/Instructors/2
Aggiunge una colonna Office che visualizza
item.OfficeAssignment.Location
solo seitem.OfficeAssignment
non è Null. Poiché questa è una relazione uno-a-zero-o-uno, potrebbe non esserci un'entità OfficeAssignment correlata.@if (item.OfficeAssignment != null) { @item.OfficeAssignment.Location }
Aggiunge una colonna Courses che visualizza i corsi tenuti da ogni insegnante. Per altre informazioni su questa razor sintassi, vedere Transizione di riga esplicita.
Aggiunge codice che aggiunge
class="table-success"
in modo dinamico all'elementotr
dell'insegnante e del corso selezionati. In questo modo viene impostato un colore di sfondo per la riga selezionata tramite una classe Bootstrap.string selectedRow = ""; if (item.CourseID == Model.CourseID) { selectedRow = "table-success"; } <tr class="@selectedRow">
Aggiunge un nuovo collegamento ipertestuale con etichetta Select. Questo collegamento invia l'ID dell'insegnante selezionato al metodo
Index
e imposta un colore di sfondo.<a asp-action="Index" asp-route-id="@item.ID">Select</a> |
Aggiunge una tabella di corsi per l'insegnante selezionato.
Aggiunge una tabella di iscrizioni degli studenti per il corso selezionato.
Eseguire l'app e selezionare la scheda Instructors (Insegnanti). La pagina visualizza ( Location
office) dall'entità correlata OfficeAssignment
. Se OfficeAssignment
è null, viene visualizzata una cella di tabella vuota.
Fare clic sul collegamento Select per un insegnante. Verranno visualizzate le modifiche dello stile delle righe e i corsi assegnati a tale insegnante.
Selezionare un corso per visualizzare l'elenco degli studenti iscritti e i voti corrispondenti.
Passaggi successivi
La prossima esercitazione illustra come aggiornare i dati correlati.
Questa esercitazione illustra come leggere e visualizzare dati correlati. I dati correlati sono dati EF Core caricati nelle proprietà di navigazione.
Le figure seguenti mostrano le pagine completate per questa esercitazione:
Caricamento eager, esplicito e lazy
Esistono diversi modi per EF Core caricare i dati correlati nelle proprietà di navigazione di un'entità:
Caricamento eager. Il caricamento eager si verifica quando una query per un solo tipo di entità carica anche entità correlate. Quando viene letta un'entità, vengono recuperati i dati correlati corrispondenti. Ciò in genere ha come risultato una query join singola che recupera tutti i dati necessari. EF Core eseguirà più query per alcuni tipi di caricamento eager. L'esecuzione di più query può essere più efficiente rispetto a una singola query gigante. Il caricamento eager viene specificato con i metodi
Include
eThenInclude
.Il caricamento eager invia più query quando è inclusa una navigazione di raccolte:
- Una query per la query principale
- Una query per ogni raccolta "perimetrale" nell'albero del caricamento.
Query separate con
Load
: i dati possono essere recuperati in query separate e EF Core "corregge" le proprietà di navigazione. "Correzioni" indica che EF Core popola automaticamente le proprietà di navigazione. Le query separate conLoad
sono più simili a un caricamento esplicito che a un caricamento eager.Nota:EF Core corregge automaticamente le proprietà di navigazione in tutte le altre entità caricate in precedenza nell'istanza di contesto. Anche se i dati per una proprietà di navigazione non sono inclusi in modo esplicito, la proprietà può comunque essere popolata se alcune o tutte le entità correlate sono state caricate in precedenza.
Caricamento esplicito Quando un'entità viene letta per la prima volta, i dati correlati non vengono recuperati. Per recuperare i dati correlati quando necessario, è necessario scrivere codice. Il caricamento esplicito con query separate ha come risultato l'invio di più query al database. Con il caricamento esplicito, il codice specifica le proprietà di navigazione da caricare. Per eseguire il caricamento esplicito, usare il metodo
Load
. Ad esempio:Caricamento lazy. Quando un'entità viene letta per la prima volta, i dati correlati non vengono recuperati. La prima volta che si accede a una proprietà di navigazione, i dati necessari per quest'ultima vengono recuperati automaticamente. Ogni volta che si accede a una proprietà di navigazione per la prima volta, viene inviata una query al database. Il caricamento differita può compromettere le prestazioni, ad esempio quando gli sviluppatori usano modelli N+1, caricando un elemento padre ed enumerando gli elementi figlio.
Creare pagine Course
L'entità Course
include una proprietà di navigazione che contiene l'entità Department
correlata.
Per visualizzare il nome del dipartimento assegnato per un corso:
- Caricare l'entità
Department
correlata nella proprietà di navigazioneCourse.Department
. - Ottenere il nome dalla proprietà
Name
dell'entitàDepartment
.
Scaffolding delle pagine Course
Seguire le istruzioni in Scaffolding delle pagine Student con le eccezioni seguenti:
- Creare una cartella Pages/Courses.
- Usare
Course
per la classe del modello. - Usare la classe di contesto esistente anziché crearne una nuova.
Aprire
Pages/Courses/Index.cshtml.cs
ed esaminare ilOnGetAsync
metodo . Il motore di scaffolding ha specificato il caricamento eager per la proprietà di navigazioneDepartment
. Il metodoInclude
specifica il caricamento eager.Eseguire l'app e selezionare il collegamento Courses (Corsi). La colonna dei dipartimenti visualizza il
DepartmentID
, che non è utile.
Visualizzare il nome del dipartimento
Aggiornare Pages/Courses/Index.cshtml.cs con il codice seguente:
using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace ContosoUniversity.Pages.Courses
{
public class IndexModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;
public IndexModel(ContosoUniversity.Data.SchoolContext context)
{
_context = context;
}
public IList<Course> Courses { get; set; }
public async Task OnGetAsync()
{
Courses = await _context.Courses
.Include(c => c.Department)
.AsNoTracking()
.ToListAsync();
}
}
}
Il codice precedente modifica la proprietà Course
in Courses
e aggiunge AsNoTracking
. AsNoTracking
migliora le prestazioni, perché le entità restituite non vengono registrate, dato che non vengono aggiornate nel contesto corrente.
Aggiornare Pages/Courses/Index.cshtml
con il codice seguente.
@page
@model ContosoUniversity.Pages.Courses.IndexModel
@{
ViewData["Title"] = "Courses";
}
<h1>Courses</h1>
<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Courses[0].CourseID)
</th>
<th>
@Html.DisplayNameFor(model => model.Courses[0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Courses[0].Credits)
</th>
<th>
@Html.DisplayNameFor(model => model.Courses[0].Department)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Courses)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.CourseID)
</td>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.Credits)
</td>
<td>
@Html.DisplayFor(modelItem => item.Department.Name)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.CourseID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.CourseID">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.CourseID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Al codice con scaffolding sono state apportate le modifiche seguenti:
Il nome della proprietà
Course
è stato modificato inCourses
.È stata aggiunta la colonna Number (Numero) con il valore della proprietà
CourseID
. Per impostazione predefinita, non viene eseguito lo scaffolding delle chiavi primarie, perché in genere non sono significative per gli utenti finali. In questo caso, tuttavia, la chiave primaria è significativa.Modificare la colonna Department (Dipartimento) per visualizzare il nome del dipartimento. Il codice visualizza la proprietà
Name
dell'entitàDepartment
che viene caricata nella proprietà di navigazioneDepartment
:@Html.DisplayFor(modelItem => item.Department.Name)
Eseguire l'app e selezionare la scheda Courses (Corsi) per visualizzare l'elenco con i nomi dei dipartimenti.
Caricamento di dati correlati con Select
Il metodo OnGetAsync
carica i dati correlati con il metodo Include
. Il metodo Select
è un'alternativa che carica solo i dati correlati necessari. Per singoli elementi, ad esempio per Department.Name
usa un INNER JOIN SQL. Per le raccolte usa un altro accesso al database, ma anche l'operatore Include
fa lo stesso sulle raccolte.
Il codice seguente carica dati correlati con il metodo Select
:
public IList<CourseViewModel> CourseVM { get; set; }
public async Task OnGetAsync()
{
CourseVM = await _context.Courses
.Select(p => new CourseViewModel
{
CourseID = p.CourseID,
Title = p.Title,
Credits = p.Credits,
DepartmentName = p.Department.Name
}).ToListAsync();
}
Il codice precedente non restituisce alcun tipo di entità, pertanto non viene eseguito alcun rilevamento. Per altre informazioni sul rilevamento di Entity Framework, vedere Tracking vs. No-Tracking Queries.For more information about the EF tracking, see Tracking vs. No-Tracking Queries.
CourseViewModel
:
public class CourseViewModel
{
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public string DepartmentName { get; set; }
}
Per un esempio completo, vedere IndexSelect.cshtml e IndexSelect.cshtml.cs.
Creare pagine Instructor
Questa sezione descrive come eseguire lo scaffolding delle pagine Instructor e come aggiungere corsi e iscrizioni correlati alla pagina di indice degli insegnanti.
Questa pagina legge e visualizza dati correlati nei modi seguenti:
- L'elenco di insegnanti visualizza dati correlati provenienti dall'entità
OfficeAssignment
, Office (Ufficio) nella figura precedente. Tra le entitàInstructor
eOfficeAssignment
c'è una relazione uno-a-zero-o-uno. Per le entitàOfficeAssignment
viene usato il caricamento eager. Il caricamento eager è in genere più efficiente quando i dati correlati devono essere visualizzati. In questo caso, devono essere visualizzate le assegnazioni di ufficio degli insegnanti. - Quando l'utente seleziona un insegnante, vengono visualizzate le entità
Course
correlate. Tra le entitàInstructor
eCourse
esiste una relazione molti-a-molti. Il caricamento eager viene usato per le entitàCourse
e le entitàDepartment
correlate. In questo caso, query separate potrebbero essere più efficienti, perché sono necessari solo i corsi per l'insegnante selezionato. Questo esempio illustra come usare il caricamento eager per le proprietà di navigazione di entità all'interno di proprietà di navigazione. - Quando l'utente seleziona un corso, vengono visualizzati i dati correlati dall'entità
Enrollments
. Nella figura precedente, vengono visualizzati il voto e il nome degli studenti. Tra le entitàCourse
eEnrollment
esiste una relazione uno-a-molti.
Creare un modello di visualizzazione
La pagina Instructors (Insegnanti) mostra i dati di tre tabelle diverse. È necessario un modello di visualizzazione che includa tre proprietà che rappresentano le tre tabelle.
Creare SchoolViewModels/InstructorIndexData.cs
con il codice seguente:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ContosoUniversity.Models.SchoolViewModels
{
public class InstructorIndexData
{
public IEnumerable<Instructor> Instructors { get; set; }
public IEnumerable<Course> Courses { get; set; }
public IEnumerable<Enrollment> Enrollments { get; set; }
}
}
Scaffolding delle pagine Instructor
Seguire le istruzioni in Scaffolding delle pagine Student con le eccezioni seguenti:
- Creare una cartella Pages/Instructors.
- Usare
Instructor
per la classe del modello. - Usare la classe di contesto esistente anziché crearne una nuova.
Per visualizzare l'aspetto della pagina con scaffolding prima di aggiornarla, eseguire l'app e passare alla pagina Instructors.
Aggiornare Pages/Instructors/Index.cshtml.cs
con il codice seguente:
using ContosoUniversity.Models;
using ContosoUniversity.Models.SchoolViewModels; // Add VM
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Threading.Tasks;
namespace ContosoUniversity.Pages.Instructors
{
public class IndexModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;
public IndexModel(ContosoUniversity.Data.SchoolContext context)
{
_context = context;
}
public InstructorIndexData InstructorData { get; set; }
public int InstructorID { get; set; }
public int CourseID { get; set; }
public async Task OnGetAsync(int? id, int? courseID)
{
InstructorData = new InstructorIndexData();
InstructorData.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Enrollments)
.ThenInclude(i => i.Student)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
if (id != null)
{
InstructorID = id.Value;
Instructor instructor = InstructorData.Instructors
.Where(i => i.ID == id.Value).Single();
InstructorData.Courses = instructor.CourseAssignments.Select(s => s.Course);
}
if (courseID != null)
{
CourseID = courseID.Value;
var selectedCourse = InstructorData.Courses
.Where(x => x.CourseID == courseID).Single();
InstructorData.Enrollments = selectedCourse.Enrollments;
}
}
}
}
Il metodo OnGetAsync
accetta i dati di route facoltativi per l'ID dell'insegnante selezionato.
Esaminare la query nel Pages/Instructors/Index.cshtml.cs
file:
InstructorData.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Enrollments)
.ThenInclude(i => i.Student)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
Il codice specifica il caricamento eager delle proprietà di navigazione seguenti:
Instructor.OfficeAssignment
Instructor.CourseAssignments
CourseAssignments.Course
Course.Department
Course.Enrollments
Enrollment.Student
Si noti la ripetizione dei metodi Include
e ThenInclude
per CourseAssignments
e Course
. Questa ripetizione è necessaria per specificare il caricamento eager per due proprietà di navigazione dell'entità Course
.
Il codice seguente viene eseguito quando viene selezionato un insegnante (id != null
).
if (id != null)
{
InstructorID = id.Value;
Instructor instructor = InstructorData.Instructors
.Where(i => i.ID == id.Value).Single();
InstructorData.Courses = instructor.CourseAssignments.Select(s => s.Course);
}
L'insegnante selezionato viene recuperato dall'elenco di insegnanti nel modello di visualizzazione. La proprietà Courses
del modello di visualizzazione viene caricata con le entità Course
dalla proprietà di navigazione CourseAssignments
di tale insegnante.
Il metodo Where
restituisce una raccolta. In questo caso, tuttavia, il filtro selezionerà una singola entità, quindi viene chiamato il Single
metodo per convertire la raccolta in una singola Instructor
entità. L'entità Instructor
consente l'accesso alla proprietà CourseAssignments
. CourseAssignments
consente l'accesso alle entità Course
correlate.
Il metodo Single
viene usato in una raccolta quando quest'ultima ha un solo elemento. Il metodo Single
genera un'eccezione se la raccolta è vuota o se contiene più elementi. In alternativa, è possibile usare SingleOrDefault
, che restituisce un valore predefinito (Null in questo caso) se la raccolta è vuota.
Il codice seguente popola la proprietà Enrollments
del modello di visualizzazione quando è selezionato un corso:
if (courseID != null)
{
CourseID = courseID.Value;
var selectedCourse = InstructorData.Courses
.Where(x => x.CourseID == courseID).Single();
InstructorData.Enrollments = selectedCourse.Enrollments;
}
Aggiornare la pagina di indice degli insegnanti
Aggiornare Pages/Instructors/Index.cshtml
con il codice seguente.
@page "{id:int?}"
@model ContosoUniversity.Pages.Instructors.IndexModel
@{
ViewData["Title"] = "Instructors";
}
<h2>Instructors</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>Last Name</th>
<th>First Name</th>
<th>Hire Date</th>
<th>Office</th>
<th>Courses</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.InstructorData.Instructors)
{
string selectedRow = "";
if (item.ID == Model.InstructorID)
{
selectedRow = "table-success";
}
<tr class="@selectedRow">
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.HireDate)
</td>
<td>
@if (item.OfficeAssignment != null)
{
@item.OfficeAssignment.Location
}
</td>
<td>
@{
foreach (var course in item.CourseAssignments)
{
@course.Course.CourseID @: @course.Course.Title <br />
}
}
</td>
<td>
<a asp-page="./Index" asp-route-id="@item.ID">Select</a> |
<a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
@if (Model.InstructorData.Courses != null)
{
<h3>Courses Taught by Selected Instructor</h3>
<table class="table">
<tr>
<th></th>
<th>Number</th>
<th>Title</th>
<th>Department</th>
</tr>
@foreach (var item in Model.InstructorData.Courses)
{
string selectedRow = "";
if (item.CourseID == Model.CourseID)
{
selectedRow = "table-success";
}
<tr class="@selectedRow">
<td>
<a asp-page="./Index" asp-route-courseID="@item.CourseID">Select</a>
</td>
<td>
@item.CourseID
</td>
<td>
@item.Title
</td>
<td>
@item.Department.Name
</td>
</tr>
}
</table>
}
@if (Model.InstructorData.Enrollments != null)
{
<h3>
Students Enrolled in Selected Course
</h3>
<table class="table">
<tr>
<th>Name</th>
<th>Grade</th>
</tr>
@foreach (var item in Model.InstructorData.Enrollments)
{
<tr>
<td>
@item.Student.FullName
</td>
<td>
@Html.DisplayFor(modelItem => item.Grade)
</td>
</tr>
}
</table>
}
Il codice precedente apporta le modifiche seguenti:
Aggiorna la direttiva
page
da@page
a@page "{id:int?}"
."{id:int?}"
è un modello di route. Il modello di route cambia le stringhe di query di tipo integer nell'URL in dati di route. Se ad esempio si fa clic su sul collegamento Select (Seleziona) per un insegnante con la sola direttiva@page
, viene generato un URL come il seguente:https://localhost:5001/Instructors?id=2
Quando la direttiva della pagina è
@page "{id:int?}"
, l'URL è:https://localhost:5001/Instructors/2
Aggiunge una colonna Office che visualizza
item.OfficeAssignment.Location
solo seitem.OfficeAssignment
non è Null. Poiché questa è una relazione uno-a-zero-o-uno, potrebbe non esserci un'entità OfficeAssignment correlata.@if (item.OfficeAssignment != null) { @item.OfficeAssignment.Location }
Aggiunge una colonna Courses che visualizza i corsi tenuti da ogni insegnante. Per altre informazioni su questa razor sintassi, vedere Transizione di riga esplicita.
Aggiunge codice che aggiunge
class="table-success"
in modo dinamico all'elementotr
dell'insegnante e del corso selezionati. In questo modo viene impostato un colore di sfondo per la riga selezionata tramite una classe Bootstrap.string selectedRow = ""; if (item.CourseID == Model.CourseID) { selectedRow = "table-success"; } <tr class="@selectedRow">
Aggiunge un nuovo collegamento ipertestuale con etichetta Select. Questo collegamento invia l'ID dell'insegnante selezionato al metodo
Index
e imposta un colore di sfondo.<a asp-action="Index" asp-route-id="@item.ID">Select</a> |
Aggiunge una tabella di corsi per l'insegnante selezionato.
Aggiunge una tabella di iscrizioni degli studenti per il corso selezionato.
Eseguire l'app e selezionare la scheda Instructors (Insegnanti). La pagina visualizza ( Location
office) dall'entità correlata OfficeAssignment
. Se OfficeAssignment
è null, viene visualizzata una cella di tabella vuota.
Fare clic sul collegamento Select per un insegnante. Verranno visualizzate le modifiche dello stile delle righe e i corsi assegnati a tale insegnante.
Selezionare un corso per visualizzare l'elenco degli studenti iscritti e i voti corrispondenti.
Usare Single
Il metodo Single
può passare la condizione Where
anziché chiamare il metodo Where
separatamente:
public async Task OnGetAsync(int? id, int? courseID)
{
InstructorData = new InstructorIndexData();
InstructorData.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Enrollments)
.ThenInclude(i => i.Student)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
if (id != null)
{
InstructorID = id.Value;
Instructor instructor = InstructorData.Instructors.Single(
i => i.ID == id.Value);
InstructorData.Courses = instructor.CourseAssignments.Select(
s => s.Course);
}
if (courseID != null)
{
CourseID = courseID.Value;
InstructorData.Enrollments = InstructorData.Courses.Single(
x => x.CourseID == courseID).Enrollments;
}
}
L'uso di Single
con una condizione Where è una questione di preferenza personale. Non offre alcun vantaggio rispetto all'uso del metodo Where
.
Caricamento esplicito
Il codice corrente specifica il caricamento eager per Enrollments
e Students
:
InstructorData.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Enrollments)
.ThenInclude(i => i.Student)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
Si supponga che gli utenti richiedano raramente la visualizzazione delle iscrizioni a un corso. In questo caso, per ottimizzare il funzionamento dell'app è utile caricare i dati delle iscrizioni solo se vengono richiesti. In questa sezione, viene eseguito l'aggiornamento di OnGetAsync
in modo da usare il caricamento esplicito di Enrollments
e Students
.
Aggiornare Pages/Instructors/Index.cshtml.cs
con il codice seguente.
using ContosoUniversity.Models;
using ContosoUniversity.Models.SchoolViewModels; // Add VM
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Threading.Tasks;
namespace ContosoUniversity.Pages.Instructors
{
public class IndexModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;
public IndexModel(ContosoUniversity.Data.SchoolContext context)
{
_context = context;
}
public InstructorIndexData InstructorData { get; set; }
public int InstructorID { get; set; }
public int CourseID { get; set; }
public async Task OnGetAsync(int? id, int? courseID)
{
InstructorData = new InstructorIndexData();
InstructorData.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
//.Include(i => i.CourseAssignments)
// .ThenInclude(i => i.Course)
// .ThenInclude(i => i.Enrollments)
// .ThenInclude(i => i.Student)
//.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
if (id != null)
{
InstructorID = id.Value;
Instructor instructor = InstructorData.Instructors
.Where(i => i.ID == id.Value).Single();
InstructorData.Courses = instructor.CourseAssignments.Select(s => s.Course);
}
if (courseID != null)
{
CourseID = courseID.Value;
var selectedCourse = InstructorData.Courses
.Where(x => x.CourseID == courseID).Single();
await _context.Entry(selectedCourse).Collection(x => x.Enrollments).LoadAsync();
foreach (Enrollment enrollment in selectedCourse.Enrollments)
{
await _context.Entry(enrollment).Reference(x => x.Student).LoadAsync();
}
InstructorData.Enrollments = selectedCourse.Enrollments;
}
}
}
}
Il codice precedente rilascia le chiamate del metodo ThenInclude per i dati delle iscrizioni e degli studenti. Se è selezionato un corso, il codice di caricamento esplicito recupera:
- Le entità
Enrollment
per il corso selezionato. - Le entità
Student
per ogni entitàEnrollment
.
Si noti che nel codice precedente .AsNoTracking()
è impostato come commento. Le proprietà di navigazione possono solo essere caricate in modo esplicito per le entità registrate.
Test dell'app. Dal punto di vista dell'utente, l'app si comporta in modo identico alla versione precedente.
Passaggi successivi
La prossima esercitazione illustra come aggiornare i dati correlati.
In questa esercitazione vengono letti e visualizzati dati correlati. I dati correlati sono dati EF Core caricati nelle proprietà di navigazione.
Se si verificano problemi non è possibile risolvere, scaricare o visualizzare l'app completata. Istruzioni per il download.
Le figure seguenti mostrano le pagine completate per questa esercitazione:
Caricamento eager, esplicito e lazy dei dati correlati
Esistono diversi modi per EF Core caricare i dati correlati nelle proprietà di navigazione di un'entità:
Caricamento eager. Il caricamento eager si verifica quando una query per un solo tipo di entità carica anche entità correlate. Quando l'entità viene letta, vengono recuperati i dati correlati corrispondenti. Ciò in genere ha come risultato una query join singola che recupera tutti i dati necessari. EF Core eseguirà più query per alcuni tipi di caricamento eager. La generazione di più query può essere più efficiente rispetto al caso di alcune query in EF6, in cui era presente una singola query. Il caricamento eager viene specificato con i metodi
Include
eThenInclude
.Il caricamento eager invia più query quando è inclusa una navigazione di raccolte:
- Una query per la query principale
- Una query per ogni raccolta "perimetrale" nell'albero del caricamento.
Query separate con
Load
: i dati possono essere recuperati in query separate e EF Core "corregge" le proprietà di navigazione. "fixes up" significa che EF Core popola automaticamente le proprietà di navigazione. Le query separate conLoad
sono più simili a un caricamento esplicito che a un caricamento eager.Nota: EF Core corregge automaticamente le proprietà di navigazione in tutte le altre entità caricate in precedenza nell'istanza di contesto. Anche se i dati per una proprietà di navigazione non sono inclusi in modo esplicito, la proprietà può comunque essere popolata se alcune o tutte le entità correlate sono state caricate in precedenza.
Caricamento esplicito Quando un'entità viene letta per la prima volta, i dati correlati non vengono recuperati. Per recuperare i dati correlati quando necessario, è necessario scrivere codice. Il caricamento esplicito con query separate ha come risultato l'invio di più query al database. Con il caricamento esplicito, il codice specifica le proprietà di navigazione da caricare. Per eseguire il caricamento esplicito, usare il metodo
Load
. Ad esempio:Caricamento lazy. Il caricamento differita è stato aggiunto a EF Core nella versione 2.1. Quando un'entità viene letta per la prima volta, i dati correlati non vengono recuperati. La prima volta che si accede a una proprietà di navigazione, i dati necessari per quest'ultima vengono recuperati automaticamente. Ogni volta che si accede a una proprietà di navigazione per la prima volta, viene inviata una query al database.
L'operatore
Select
carica solo i dati correlati necessari.
Creare una pagina Course che visualizza il nome dei dipartimenti
L'entità Course include una proprietà di navigazione che contiene l'entità Department
. L'entità Department
contiene il dipartimento a cui il corso è assegnato.
Per visualizzare il nome del dipartimento assegnato in un elenco dei corsi:
- Ottenere la proprietà
Name
dall'entitàDepartment
. - L'entità
Department
proviene dalla proprietà di navigazioneCourse.Department
.
Scaffolding del modello Course
Seguire le istruzioni in Eseguire lo scaffolding del modello Student (Studente) e usare Course
per la classe modello.
Il comando precedente esegue lo scaffolding del modello Course
. Aprire il progetto in Visual Studio.
Aprire Pages/Courses/Index.cshtml.cs
ed esaminare il OnGetAsync
metodo . Il motore di scaffolding ha specificato il caricamento eager per la proprietà di navigazione Department
. Il metodo Include
specifica il caricamento eager.
Eseguire l'app e selezionare il collegamento Courses (Corsi). La colonna dei dipartimenti visualizza il DepartmentID
, che non è utile.
Aggiornare il metodo OnGetAsync
con il codice seguente:
public async Task OnGetAsync()
{
Course = await _context.Courses
.Include(c => c.Department)
.AsNoTracking()
.ToListAsync();
}
Il codice precedente aggiunge AsNoTracking
. AsNoTracking
migliora le prestazioni, perché le entità restituite non vengono registrate, dato che non vengono aggiornate nel contesto corrente.
Eseguire l'aggiornamento Pages/Courses/Index.cshtml
con il markup evidenziato seguente:
@page
@model ContosoUniversity.Pages.Courses.IndexModel
@{
ViewData["Title"] = "Courses";
}
<h2>Courses</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Course[0].CourseID)
</th>
<th>
@Html.DisplayNameFor(model => model.Course[0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Course[0].Credits)
</th>
<th>
@Html.DisplayNameFor(model => model.Course[0].Department)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Course)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.CourseID)
</td>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.Credits)
</td>
<td>
@Html.DisplayFor(modelItem => item.Department.Name)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.CourseID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.CourseID">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.CourseID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Al codice con scaffolding sono state apportate le modifiche seguenti:
Il titolo è stato modificato da Index (Indice) a Courses (Corsi).
È stata aggiunta la colonna Number (Numero) con il valore della proprietà
CourseID
. Per impostazione predefinita, non viene eseguito lo scaffolding delle chiavi primarie, perché in genere non sono significative per gli utenti finali. In questo caso, tuttavia, la chiave primaria è significativa.Modificare la colonna Department (Dipartimento) per visualizzare il nome del dipartimento. Il codice visualizza la proprietà
Name
dell'entitàDepartment
che viene caricata nella proprietà di navigazioneDepartment
:@Html.DisplayFor(modelItem => item.Department.Name)
Eseguire l'app e selezionare la scheda Courses (Corsi) per visualizzare l'elenco con i nomi dei dipartimenti.
Caricamento di dati correlati con Select
Il metodo OnGetAsync
carica i dati correlati con il metodo Include
:
public async Task OnGetAsync()
{
Course = await _context.Courses
.Include(c => c.Department)
.AsNoTracking()
.ToListAsync();
}
L'operatore Select
carica solo i dati correlati necessari. Per singoli elementi, ad esempio per Department.Name
usa un INNER JOIN SQL. Per le raccolte usa un altro accesso al database, ma anche l'operatore Include
fa lo stesso sulle raccolte.
Il codice seguente carica dati correlati con il metodo Select
:
public IList<CourseViewModel> CourseVM { get; set; }
public async Task OnGetAsync()
{
CourseVM = await _context.Courses
.Select(p => new CourseViewModel
{
CourseID = p.CourseID,
Title = p.Title,
Credits = p.Credits,
DepartmentName = p.Department.Name
}).ToListAsync();
}
CourseViewModel
:
public class CourseViewModel
{
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public string DepartmentName { get; set; }
}
Per un esempio completo, vedere IndexSelect.cshtml e IndexSelect.cshtml.cs.
Creare una pagina Instructors (Insegnanti) che mostri i corsi e le iscrizioni
In questa sezione viene creata la pagina Instructors (Insegnanti).
Questa pagina legge e visualizza dati correlati nei modi seguenti:
- L'elenco di insegnanti visualizza dati correlati provenienti dall'entità
OfficeAssignment
, Office (Ufficio) nella figura precedente. Tra le entitàInstructor
eOfficeAssignment
c'è una relazione uno-a-zero-o-uno. Per le entitàOfficeAssignment
viene usato il caricamento eager. Il caricamento eager è in genere più efficiente quando i dati correlati devono essere visualizzati. In questo caso, devono essere visualizzate le assegnazioni di ufficio degli insegnanti. - Quando l'utente seleziona un insegnante (Harui nella figura precedente), vengono visualizzate le entità
Course
correlate. Tra le entitàInstructor
eCourse
esiste una relazione molti-a-molti. Il caricamento eager viene usato per le entitàCourse
e le entitàDepartment
correlate. In questo caso, query separate potrebbero essere più efficienti, perché sono necessari solo i corsi per l'insegnante selezionato. Questo esempio illustra come usare il caricamento eager per le proprietà di navigazione di entità all'interno di proprietà di navigazione. - Quando l'utente seleziona un corso (Chemistry (Chimica) nella figura precedente), vengono visualizzati i dati correlati dell'entità
Enrollments
. Nella figura precedente, vengono visualizzati il voto e il nome degli studenti. Tra le entitàCourse
eEnrollment
esiste una relazione uno-a-molti.
Creare un modello per la visualizzazione dell'indice degli insegnanti
La pagina Instructors (Insegnanti) mostra i dati di tre tabelle diverse. Viene creato un modello di visualizzazione che include le tre entità che rappresentano le tre tabelle.
Nella cartella SchoolViewModels creare InstructorIndexData.cs
con il codice seguente:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ContosoUniversity.Models.SchoolViewModels
{
public class InstructorIndexData
{
public IEnumerable<Instructor> Instructors { get; set; }
public IEnumerable<Course> Courses { get; set; }
public IEnumerable<Enrollment> Enrollments { get; set; }
}
}
Scaffolding del modello Instructor
Seguire le istruzioni in Eseguire lo scaffolding del modello Student (Studente) e usare Instructor
per la classe modello.
Il comando precedente esegue lo scaffolding del modello Instructor
.
Eseguire l'app e passare alla pagina Instructors (Insegnanti).
Sostituisci Pages/Instructors/Index.cshtml.cs
con il seguente codice:
using ContosoUniversity.Models;
using ContosoUniversity.Models.SchoolViewModels; // Add VM
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Threading.Tasks;
namespace ContosoUniversity.Pages.Instructors
{
public class IndexModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;
public IndexModel(ContosoUniversity.Data.SchoolContext context)
{
_context = context;
}
public InstructorIndexData Instructor { get; set; }
public int InstructorID { get; set; }
public async Task OnGetAsync(int? id)
{
Instructor = new InstructorIndexData();
Instructor.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
if (id != null)
{
InstructorID = id.Value;
}
}
}
}
Il metodo OnGetAsync
accetta i dati di route facoltativi per l'ID dell'insegnante selezionato.
Esaminare la query nel Pages/Instructors/Index.cshtml.cs
file:
Instructor.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
La query ha due istruzioni Include, che riguardano:
OfficeAssignment
: visualizzato nella visualizzazione degli insegnanti.CourseAssignments
: visualizza i corsi tenuti.
Aggiornare la pagina di indice degli insegnanti
Eseguire l'aggiornamento Pages/Instructors/Index.cshtml
con il markup seguente:
@page "{id:int?}"
@model ContosoUniversity.Pages.Instructors.IndexModel
@{
ViewData["Title"] = "Instructors";
}
<h2>Instructors</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>Last Name</th>
<th>First Name</th>
<th>Hire Date</th>
<th>Office</th>
<th>Courses</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Instructor.Instructors)
{
string selectedRow = "";
if (item.ID == Model.InstructorID)
{
selectedRow = "success";
}
<tr class="@selectedRow">
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.HireDate)
</td>
<td>
@if (item.OfficeAssignment != null)
{
@item.OfficeAssignment.Location
}
</td>
<td>
@{
foreach (var course in item.CourseAssignments)
{
@course.Course.CourseID @: @course.Course.Title <br />
}
}
</td>
<td>
<a asp-page="./Index" asp-route-id="@item.ID">Select</a> |
<a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Il markup precedente apporta le modifiche seguenti:
Aggiorna la direttiva
page
da@page
a@page "{id:int?}"
."{id:int?}"
è un modello di route. Il modello di route cambia le stringhe di query di tipo integer nell'URL in dati di route. Se ad esempio si fa clic su sul collegamento Select (Seleziona) per un insegnante con la sola direttiva@page
, viene generato un URL come il seguente:http://localhost:1234/Instructors?id=2
Quando la direttiva della pagina è
@page "{id:int?}"
, l'URL precedente è:http://localhost:1234/Instructors/2
Il titolo pagina è Instructors (Insegnanti).
È stata aggiunta la colonna Office (Ufficio) che visualizza
item.OfficeAssignment.Location
solo seitem.OfficeAssignment
non è Null. Poiché questa è una relazione uno-a-zero-o-uno, potrebbe non esserci un'entità OfficeAssignment correlata.@if (item.OfficeAssignment != null) { @item.OfficeAssignment.Location }
È stata aggiunta la colonna Courses (Corsi) che visualizza i corsi tenuti da ogni insegnante. Per altre informazioni su questa razor sintassi, vedere Transizione di riga esplicita.
È stato aggiunto codice che aggiunge
class="success"
in modo dinamico all'elementotr
dell'insegnante selezionato. In questo modo viene impostato un colore di sfondo per la riga selezionata tramite una classe Bootstrap.string selectedRow = ""; if (item.CourseID == Model.CourseID) { selectedRow = "success"; } <tr class="@selectedRow">
È stato aggiunto un nuovo collegamento ipertestuale con etichetta Select (Seleziona). Questo collegamento invia l'ID dell'insegnante selezionato al metodo
Index
e imposta un colore di sfondo.<a asp-action="Index" asp-route-id="@item.ID">Select</a> |
Eseguire l'app e selezionare la scheda Instructors (Insegnanti). La pagina visualizza ( Location
office) dall'entità correlata OfficeAssignment
. Se OfficeAssignment è Null, nella tabella viene visualizzata una cella vuota.
Fare clic sul collegamento Select (Seleziona). Lo stile delle righe cambia.
Aggiungere corsi tenuti dall'insegnante selezionato
Aggiornare il OnGetAsync
metodo in Pages/Instructors/Index.cshtml.cs
con il codice seguente:
public async Task OnGetAsync(int? id, int? courseID)
{
Instructor = new InstructorIndexData();
Instructor.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
if (id != null)
{
InstructorID = id.Value;
Instructor instructor = Instructor.Instructors.Where(
i => i.ID == id.Value).Single();
Instructor.Courses = instructor.CourseAssignments.Select(s => s.Course);
}
if (courseID != null)
{
CourseID = courseID.Value;
Instructor.Enrollments = Instructor.Courses.Where(
x => x.CourseID == courseID).Single().Enrollments;
}
}
Aggiungere public int CourseID { get; set; }
public class IndexModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;
public IndexModel(ContosoUniversity.Data.SchoolContext context)
{
_context = context;
}
public InstructorIndexData Instructor { get; set; }
public int InstructorID { get; set; }
public int CourseID { get; set; }
public async Task OnGetAsync(int? id, int? courseID)
{
Instructor = new InstructorIndexData();
Instructor.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
if (id != null)
{
InstructorID = id.Value;
Instructor instructor = Instructor.Instructors.Where(
i => i.ID == id.Value).Single();
Instructor.Courses = instructor.CourseAssignments.Select(s => s.Course);
}
if (courseID != null)
{
CourseID = courseID.Value;
Instructor.Enrollments = Instructor.Courses.Where(
x => x.CourseID == courseID).Single().Enrollments;
}
}
Esaminare la query aggiornata:
Instructor.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
La query precedente aggiunge l'entità Department
.
Il codice seguente viene eseguito quando viene selezionato un insegnante (id != null
). L'insegnante selezionato viene recuperato dall'elenco di insegnanti nel modello di visualizzazione. La proprietà Courses
del modello di visualizzazione viene caricata con le entità Course
dalla proprietà di navigazione CourseAssignments
di tale insegnante.
if (id != null)
{
InstructorID = id.Value;
Instructor instructor = Instructor.Instructors.Where(
i => i.ID == id.Value).Single();
Instructor.Courses = instructor.CourseAssignments.Select(s => s.Course);
}
Il metodo Where
restituisce una raccolta. Nel metodo Where
precedente viene restituita una sola entità Instructor
. Il metodo Single
converte la raccolta in un'unica entità Instructor
. L'entità Instructor
consente l'accesso alla proprietà CourseAssignments
. CourseAssignments
consente l'accesso alle entità Course
correlate.
Il metodo Single
viene usato in una raccolta quando quest'ultima ha un solo elemento. Il metodo Single
genera un'eccezione se la raccolta è vuota o se contiene più elementi. In alternativa, è possibile usare SingleOrDefault
, che restituisce un valore predefinito (Null in questo caso) se la raccolta è vuota. L'uso di SingleOrDefault
per una raccolta vuota:
- Genera un'eccezione, a causa del tentativo di cercare la proprietà
Courses
in un riferimento Null. - Il messaggio di eccezione indica meno chiaramente la causa del problema.
Il codice seguente popola la proprietà Enrollments
del modello di visualizzazione quando è selezionato un corso:
if (courseID != null)
{
CourseID = courseID.Value;
Instructor.Enrollments = Instructor.Courses.Where(
x => x.CourseID == courseID).Single().Enrollments;
}
Aggiungere il markup seguente alla fine della Pages/Instructors/Index.cshtml
Razor pagina:
<a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
@if (Model.Instructor.Courses != null)
{
<h3>Courses Taught by Selected Instructor</h3>
<table class="table">
<tr>
<th></th>
<th>Number</th>
<th>Title</th>
<th>Department</th>
</tr>
@foreach (var item in Model.Instructor.Courses)
{
string selectedRow = "";
if (item.CourseID == Model.CourseID)
{
selectedRow = "success";
}
<tr class="@selectedRow">
<td>
<a asp-page="./Index" asp-route-courseID="@item.CourseID">Select</a>
</td>
<td>
@item.CourseID
</td>
<td>
@item.Title
</td>
<td>
@item.Department.Name
</td>
</tr>
}
</table>
}
Quando è selezionato un insegnante, il markup precedente visualizza un elenco dei corsi correlati all'insegnante stesso.
Test dell'app. Fare clic su un collegamento Select (Seleziona) nella pagina Instructors (Insegnanti).
Visualizzare i dati degli studenti
In questa sezione, l'app viene aggiornata in modo da visualizzare i dati degli studenti per il corso selezionato.
Aggiornare la query nel OnGetAsync
metodo in Pages/Instructors/Index.cshtml.cs
con il codice seguente:
Instructor.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Enrollments)
.ThenInclude(i => i.Student)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
Aggiornare Pages/Instructors/Index.cshtml
. Aggiungere il markup seguente alla fine del file:
@if (Model.Instructor.Enrollments != null)
{
<h3>
Students Enrolled in Selected Course
</h3>
<table class="table">
<tr>
<th>Name</th>
<th>Grade</th>
</tr>
@foreach (var item in Model.Instructor.Enrollments)
{
<tr>
<td>
@item.Student.FullName
</td>
<td>
@Html.DisplayFor(modelItem => item.Grade)
</td>
</tr>
}
</table>
}
Il markup precedente visualizza l'elenco degli studenti iscritti al corso selezionato.
Aggiornare la pagina e selezionare un insegnante. Selezionare un corso per visualizzare l'elenco degli studenti iscritti e i voti corrispondenti.
Usare Single
Il metodo Single
può passare la condizione Where
anziché chiamare il metodo Where
separatamente:
public async Task OnGetAsync(int? id, int? courseID)
{
Instructor = new InstructorIndexData();
Instructor.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Enrollments)
.ThenInclude(i => i.Student)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
if (id != null)
{
InstructorID = id.Value;
Instructor instructor = Instructor.Instructors.Single(
i => i.ID == id.Value);
Instructor.Courses = instructor.CourseAssignments.Select(
s => s.Course);
}
if (courseID != null)
{
CourseID = courseID.Value;
Instructor.Enrollments = Instructor.Courses.Single(
x => x.CourseID == courseID).Enrollments;
}
}
L'approccio Single
precedente non offre alcun vantaggio rispetto all'uso di Where
. Alcuni sviluppatori preferiscono lo stile dell'approccio Single
.
Caricamento esplicito
Il codice corrente specifica il caricamento eager per Enrollments
e Students
:
Instructor.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Enrollments)
.ThenInclude(i => i.Student)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
Si supponga che gli utenti richiedano raramente la visualizzazione delle iscrizioni a un corso. In questo caso, per ottimizzare il funzionamento dell'app è utile caricare i dati delle iscrizioni solo se vengono richiesti. In questa sezione, viene eseguito l'aggiornamento di OnGetAsync
in modo da usare il caricamento esplicito di Enrollments
e Students
.
Aggiornare OnGetAsync
con il codice seguente:
public async Task OnGetAsync(int? id, int? courseID)
{
Instructor = new InstructorIndexData();
Instructor.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
//.Include(i => i.CourseAssignments)
// .ThenInclude(i => i.Course)
// .ThenInclude(i => i.Enrollments)
// .ThenInclude(i => i.Student)
// .AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
if (id != null)
{
InstructorID = id.Value;
Instructor instructor = Instructor.Instructors.Where(
i => i.ID == id.Value).Single();
Instructor.Courses = instructor.CourseAssignments.Select(s => s.Course);
}
if (courseID != null)
{
CourseID = courseID.Value;
var selectedCourse = Instructor.Courses.Where(x => x.CourseID == courseID).Single();
await _context.Entry(selectedCourse).Collection(x => x.Enrollments).LoadAsync();
foreach (Enrollment enrollment in selectedCourse.Enrollments)
{
await _context.Entry(enrollment).Reference(x => x.Student).LoadAsync();
}
Instructor.Enrollments = selectedCourse.Enrollments;
}
}
Il codice precedente rilascia le chiamate del metodo ThenInclude per i dati delle iscrizioni e degli studenti. Se è selezionato un corso, il codice evidenziato recupera:
- Le entità
Enrollment
per il corso selezionato. - Le entità
Student
per ogni entitàEnrollment
.
Si noti che nel codice precedente .AsNoTracking()
è commentato. Le proprietà di navigazione possono solo essere caricate in modo esplicito per le entità registrate.
Test dell'app. Dal punto di vista degli utenti, l'app si comporta in modo identico alla versione precedente.
La prossima esercitazione illustra come aggiornare i dati correlati.