Esercitazione: Leggere i dati correlati - ASP.NET MVC con EF Core

Nell'esercitazione precedente è stato completato il modello di dati School. In questa esercitazione verranno letti e visualizzati dati correlati, ovvero dati che Entity Framework carica all'interno delle proprietà di navigazione.

Le figure seguenti illustrano le pagine che verranno usate.

Courses Index page

Instructors Index page

In questa esercitazione:

  • Scoprire come caricare i dati correlati
  • Creare una pagina Courses
  • Creare una pagina Instructors
  • Ottenere informazioni sul caricamento esplicito

Prerequisiti

Il software ORM (Object-Relational Mapping), ad esempio Entity Framework, può caricare dati correlati nelle proprietà di navigazione di un'entità in diversi modi:

  • Caricamento eager: quando l'entità viene letta, i dati correlati vengono recuperati insieme a esso. Ciò in genere ha come risultato una query join singola che recupera tutti i dati necessari. Per specificare il caricamento eager in Entity Framework Core si usano i metodi Include e ThenInclude.

    Eager loading example

    È possibile recuperare alcuni dati tramite query separate. In questo caso EF "corregge" le proprietà di navigazione. In altre parole, EF aggiunge automaticamente le entità recuperate separatamente nelle proprietà di navigazione corrispondenti delle entità recuperate in precedenza. Per la query che recupera i dati correlati, è possibile usare il metodo Load anziché un metodo che restituisce un elenco o un oggetto, ad esempio ToList o Single.

    Separate queries example

  • Caricamento esplicito: quando l'entità viene letta per la prima volta, i dati correlati non vengono recuperati. Il codice del caricamento consente di recuperare i dati correlati se sono necessari. Come nel caso del caricamento eager con query separate, il caricamento esplicito ha come risultato l'invio di più query al database. La differenza è che con il caricamento esplicito il codice specifica le proprietà di navigazione da caricare. In Entity Framework Core 1.1, per eseguire il caricamento esplicito è possibile usare il metodo Load. Ad esempio:

    Explicit loading example

  • Caricamento differita: quando l'entità viene letta per la prima volta, i dati correlati non vengono recuperati. La prima volta che si tenta di accedere a una proprietà di navigazione, tuttavia, i dati necessari per quest'ultima vengono recuperati automaticamente. Una query viene inviata al database ogni volta che si tenta di ottenere dati da una proprietà di navigazione per la prima volta. Entity Framework Core 1.0 non supporta il caricamento lazy.

Considerazioni sulle prestazioni

Se si sa di aver bisogno di dati correlati per tutte le entità recuperate, il caricamento eager spesso garantisce le prestazioni migliori, perché l'invio di un'unica query al database è in genere più efficiente dell'invio di query separate per ogni entità recuperata. Si supponga ad esempio che ogni dipartimento abbia dieci corsi correlati. Il caricamento eager di tutti i dati correlati comporta un'unica query (join) e un unico round trip al database. Una query separata per i corsi di ogni dipartimento comporta undici round trip al database. I round trip aggiuntivi al database influiscono in modo particolarmente negativo sulle prestazioni quando la latenza è elevata.

D'altra parte, in alcuni scenari query separate sono più efficienti. Il caricamento eager di tutti i dati correlati in una sola query può causare la generazione di un join molto complesso, che SQL Server non è in grado di elaborare in modo efficiente. Oppure se è necessario accedere alle proprietà di navigazione di un'entità solo per un subset di un set di entità in corso di elaborazione, query separate potrebbero offrire prestazioni migliori perché il caricamento immediato di tutti i dati recupererebbe più dati di quelli necessari. Se le prestazioni rappresentano un aspetto essenziale, per avere la certezza di scegliere il metodo più efficiente è consigliabile testare le prestazioni di entrambi i tipi di caricamento.

Creare una pagina Courses

L'entità Course include una proprietà di navigazione che contiene l'entità Department del reparto a cui è assegnato il corso. Per visualizzare il nome del reparto assegnato in un elenco di corsi, è necessario ottenere la Name proprietà dall'entità Department che si trova nella Course.Department proprietà di navigazione.

Creare un controller denominato CoursesController per il Course tipo di entità usando le stesse opzioni per il controller MVC con viste, usando lo scaffolder di Entity Framework precedentemente eseguito per StudentsController, come illustrato nella figura seguente:

Add Courses controller

Aprire CoursesController.cs ed esaminare il Index metodo . Lo scaffolding automatico ha specificato il caricamento eager per la proprietà di navigazione Department tramite il metodo Include.

Sostituire il metodo Index con il codice seguente, che usa un nome più appropriato per l'IQueryable che restituisce entità Course (courses anziché schoolContext):

public async Task<IActionResult> Index()
{
    var courses = _context.Courses
        .Include(c => c.Department)
        .AsNoTracking();
    return View(await courses.ToListAsync());
}

Aprire Views/Courses/Index.cshtml e sostituire il codice del modello con il codice seguente. Le modifiche sono evidenziate:

@model IEnumerable<ContosoUniversity.Models.Course>

@{
    ViewData["Title"] = "Courses";
}

<h2>Courses</h2>

<p>
    <a asp-action="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.CourseID)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Title)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Credits)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Department)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model)
        {
            <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-action="Edit" asp-route-id="@item.CourseID">Edit</a> |
                    <a asp-action="Details" asp-route-id="@item.CourseID">Details</a> |
                    <a asp-action="Delete" asp-route-id="@item.CourseID">Delete</a>
                </td>
            </tr>
        }
    </tbody>
</table>

Al codice con scaffolding sono state apportate le modifiche seguenti:

  • Modifica dell'intestazione da Indice a 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 ed è necessario visualizzarla.

  • 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 navigazione Department:

    @Html.DisplayFor(modelItem => item.Department.Name)
    

Eseguire l'app e selezionare la scheda Courses (Corsi) per visualizzare l'elenco con i nomi dei dipartimenti.

Courses Index page

Creare una pagina Instructors

In questa sezione si creerà un controller e si visualizzerà l'entità per visualizzare la pagina Instructors :In this section, you'll create a controller and view for the Instructor entity in to display the Instructors page:

Instructors Index page

Questa pagina legge e visualizza dati correlati nei modi seguenti:

  • L'elenco degli insegnanti visualizza i dati correlati dell'entità OfficeAssignment . Tra le entità Instructor e OfficeAssignment c'è una relazione uno-a-zero-o-uno. Si userà il caricamento eager per le OfficeAssignment entità. Come spiegato in precedenza, il caricamento eager è in genere più efficiente quando sono necessari i dati correlati per tutte le righe recuperate della tabella primaria. In questo caso, si vogliono visualizzare le assegnazioni di ufficio per tutti gli insegnanti visualizzati.

  • Quando l'utente seleziona un insegnante, vengono visualizzate le entità Course correlate. Tra le entità Instructor e Course esiste una relazione molti-a-molti. Si userà il caricamento eager per le Course entità e le relative entità correlate Department . In questo caso, query separate potrebbero essere più efficienti, perché i corsi sono necessari solo per l'insegnante selezionato. Questo esempio, tuttavia, illustra come usare il caricamento eager per le proprietà di navigazione con entità esse stesse all'interno di proprietà di navigazione.

  • Quando l'utente seleziona un corso, vengono visualizzati i dati correlati dal Enrollments set di entità. Tra le entità Course e Enrollment esiste una relazione uno-a-molti. Si useranno query separate per Enrollment le entità e le relative entità correlate Student .

Creare un modello per la visualizzazione dell'indice degli insegnanti

La pagina Instructors (Insegnanti) mostra i dati di tre tabelle diverse. Verrà quindi creato un modello di visualizzazione che includa tre proprietà, ognuna contenente i dati di una delle tabelle.

Nella cartella SchoolViewModels creare InstructorIndexData.cs e sostituire il codice esistente 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; }
    }
}

Creare il controller e le visualizzazioni per gli insegnanti

Creare un controller per gli insegnanti con azioni di lettura/scrittura EF come illustrato nella figura seguente:

Add Instructors controller

Aprire InstructorsController.cs e aggiungere un'istruzione using per lo spazio dei nomi ViewModels:

using ContosoUniversity.Models.SchoolViewModels;

Sostituire il metodo Index con il codice seguente per eseguire il caricamento eager di dati correlati e inserirlo nel modello di visualizzazione.

public async Task<IActionResult> Index(int? id, int? courseID)
{
    var viewModel = new InstructorIndexData();
    viewModel.Instructors = await _context.Instructors
          .Include(i => i.OfficeAssignment)
          .Include(i => i.CourseAssignments)
            .ThenInclude(i => i.Course)
                .ThenInclude(i => i.Enrollments)
                    .ThenInclude(i => i.Student)
          .Include(i => i.CourseAssignments)
            .ThenInclude(i => i.Course)
                .ThenInclude(i => i.Department)
          .AsNoTracking()
          .OrderBy(i => i.LastName)
          .ToListAsync();
    
    if (id != null)
    {
        ViewData["InstructorID"] = id.Value;
        Instructor instructor = viewModel.Instructors.Where(
            i => i.ID == id.Value).Single();
        viewModel.Courses = instructor.CourseAssignments.Select(s => s.Course);
    }

    if (courseID != null)
    {
        ViewData["CourseID"] = courseID.Value;
        viewModel.Enrollments = viewModel.Courses.Where(
            x => x.CourseID == courseID).Single().Enrollments;
    }

    return View(viewModel);
}

Il metodo accetta dati di route facoltativi (id) e un parametro di stringa di query (courseID) che forniscono i valori relativi all'ID dell'insegnante e del corso selezionati. I parametri sono forniti dai collegamenti ipertestuali Select (Seleziona) nella pagina.

Il codice inizia creando un'istanza del modello di visualizzazione e inserendola nell'elenco degli insegnanti. Il codice specifica il caricamento eager delle proprietà di navigazione Instructor.OfficeAssignment e Instructor.CourseAssignments. All'interno della proprietà CourseAssignments viene caricata la proprietà Course e all'interno di questa vengono caricate le proprietà Enrollments e Department, e all'interno di ogni entità Enrollment viene caricata la proprietà Student.

viewModel.Instructors = await _context.Instructors
      .Include(i => i.OfficeAssignment)
      .Include(i => i.CourseAssignments)
        .ThenInclude(i => i.Course)
            .ThenInclude(i => i.Enrollments)
                .ThenInclude(i => i.Student)
      .Include(i => i.CourseAssignments)
        .ThenInclude(i => i.Course)
            .ThenInclude(i => i.Department)
      .AsNoTracking()
      .OrderBy(i => i.LastName)
      .ToListAsync();

Poiché la vista richiede sempre l'entità OfficeAssignment , è più efficiente recuperarla nella stessa query. Le entità Course sono necessarie quando viene selezionato un insegnante nella pagina Web. Un'unica query, quindi, è più efficiente di più query solo se nella maggior parte dei casi la pagina viene visualizzata con un corso selezionato.

Il codice ripete CourseAssignments e Course perché da Course sono necessarie due proprietà. La prima stringa delle chiamate ThenInclude ottiene CourseAssignment.Course, Course.Enrollments e Enrollment.Student.

Altre informazioni sull'inclusione di più livelli di dati correlati sono disponibili qui.

viewModel.Instructors = await _context.Instructors
      .Include(i => i.OfficeAssignment)
      .Include(i => i.CourseAssignments)
        .ThenInclude(i => i.Course)
            .ThenInclude(i => i.Enrollments)
                .ThenInclude(i => i.Student)
      .Include(i => i.CourseAssignments)
        .ThenInclude(i => i.Course)
            .ThenInclude(i => i.Department)
      .AsNoTracking()
      .OrderBy(i => i.LastName)
      .ToListAsync();

A questo punto nel codice, un'altra chiamata ThenInclude riguarda le proprietà di navigazione di Student, che non sono necessarie. Ma chiamando Include il codice ricomincia con le proprietà di Instructor. È quindi necessario ripetere la sequenza, questa volta specificando Course.Department anziché Course.Enrollments.

viewModel.Instructors = await _context.Instructors
      .Include(i => i.OfficeAssignment)
      .Include(i => i.CourseAssignments)
        .ThenInclude(i => i.Course)
            .ThenInclude(i => i.Enrollments)
                .ThenInclude(i => i.Student)
      .Include(i => i.CourseAssignments)
        .ThenInclude(i => i.Course)
            .ThenInclude(i => i.Department)
      .AsNoTracking()
      .OrderBy(i => i.LastName)
      .ToListAsync();

Il codice seguente viene eseguito quando è stato selezionato un insegnante. L'insegnante selezionato viene recuperato dall'elenco di insegnanti nel modello di visualizzazione. La proprietà del modello di Courses visualizzazione viene quindi caricata con le Course entità dalla proprietà di navigazione dell'insegnante CourseAssignments .

if (id != null)
{
    ViewData["InstructorID"] = id.Value;
    Instructor instructor = viewModel.Instructors.Where(
        i => i.ID == id.Value).Single();
    viewModel.Courses = instructor.CourseAssignments.Select(s => s.Course);
}

Il metodo Where restituisce una raccolta, ma in questo caso i criteri passati a tale metodo hanno come risultato la restituzione di una sola entità Instructor. Il Single metodo converte la raccolta in una singola Instructor entità, che consente di accedere alla proprietà dell'entità CourseAssignments . La proprietà CourseAssignments contiene entità CourseAssignment, di cui si vogliono solo le entità Course correlate.

Usare il metodo Single per una raccolta quando si sa che la raccolta ha un solo elemento. Il Single metodo genera un'eccezione se la raccolta viene passata a essa vuota o se è presente più di un elemento. In alternativa, è possibile usare SingleOrDefault, che restituisce un valore predefinito (Null in questo caso) se la raccolta è vuota. In questo caso, tuttavia, ciò avrebbe comunque come risultato un'eccezione (a causa del tentativo di cercare una proprietà Courses in un riferimento Null) e il messaggio di eccezione indicherebbe meno chiaramente la causa del problema. Quando si chiama il metodo Single, è anche possibile passare la condizione Where anziché chiamare il metodo Where separatamente:

.Single(i => i.ID == id.Value)

Invece di:

.Where(i => i.ID == id.Value).Single()

Se è stato selezionato un corso, questo viene quindi recuperato dall'elenco dei corsi nel modello di visualizzazione. La proprietà Enrollments del modello di visualizzazione viene quindi caricata con le entità Enrollment dalla proprietà di navigazione Enrollments di tale corso.

if (courseID != null)
{
    ViewData["CourseID"] = courseID.Value;
    viewModel.Enrollments = viewModel.Courses.Where(
        x => x.CourseID == courseID).Single().Enrollments;
}

Rilevamento e nessun rilevamento

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.

Modificare la visualizzazione dell'indice degli insegnanti

In Views/Instructors/Index.cshtmlsostituire il codice del modello con il codice seguente. Le modifiche sono evidenziate.

@model ContosoUniversity.Models.SchoolViewModels.InstructorIndexData

@{
    ViewData["Title"] = "Instructors";
}

<h2>Instructors</h2>

<p>
    <a asp-action="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.Instructors)
        {
            string selectedRow = "";
            if (item.ID == (int?)ViewData["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-action="Index" asp-route-id="@item.ID">Select</a> |
                    <a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
                    <a asp-action="Details" asp-route-id="@item.ID">Details</a> |
                    <a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
                </td>
            </tr>
           }
    </tbody>
</table>
@model ContosoUniversity.Models.SchoolViewModels.InstructorIndexData

@{
    ViewData["Title"] = "Instructors";
}

<h2>Instructors</h2>

<p>
    <a asp-action="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.Instructors)
        {
            string selectedRow = "";
            if (item.ID == (int?)ViewData["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-action="Index" asp-route-id="@item.ID">Select</a> |
                    <a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
                    <a asp-action="Details" asp-route-id="@item.ID">Details</a> |
                    <a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
                </td>
            </tr>
           }
    </tbody>
</table>

Al codice esistente sono state apportate le modifiche seguenti:

  • La classe del modello è stata modificata in InstructorIndexData.

  • Il titolo pagina è stato modificato da Index (Indice) a Instructors (Insegnanti).

  • È stata aggiunta la colonna Office (Ufficio) che visualizza item.OfficeAssignment.Location solo se item.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, vedere la sezione Transizione di riga esplicita dell'articolo sulla Razor sintassi.

  • Aggiunta del codice che aggiunge in modo condizionale una classe CSS Bootstrap all'elemento dell'insegnante tr selezionato. Questa classe imposta un colore di sfondo per la riga selezionata.

  • È stato aggiunto un nuovo collegamento ipertestuale con etichetta Select (Seleziona) immediatamente prima degli altri collegamenti in ogni riga.Ciò comporta l'invio dell'ID dell'insegnante selezionato al metodo Index.

    <a asp-action="Index" asp-route-id="@item.ID">Select</a> |
    

Eseguire l'app e selezionare la scheda Instructors (Insegnanti). Nella pagina viene visualizzata la proprietà Location delle entità OfficeAssignment correlate e una cella di tabella vuota quando non è presente alcuna entità OfficeAssignment correlata.

Instructors Index page nothing selected

Views/Instructors/Index.cshtml Nel file, dopo l'elemento della tabella di chiusura (alla fine del file), aggiungere il codice seguente. Quando è selezionato un insegnante, Questo codice visualizza un elenco dei corsi correlati all'insegnante stesso.


@if (Model.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.Courses)
        {
            string selectedRow = "";
            if (item.CourseID == (int?)ViewData["CourseID"])
            {
                selectedRow = "success";
            }
            <tr class="@selectedRow">
                <td>
                    @Html.ActionLink("Select", "Index", new { courseID = item.CourseID })
                </td>
                <td>
                    @item.CourseID
                </td>
                <td>
                    @item.Title
                </td>
                <td>
                    @item.Department.Name
                </td>
            </tr>
        }

    </table>
}

Questo codice legge la proprietà Courses del modello di visualizzazione per visualizzare l'elenco dei corsi. Fornisce anche il collegamento ipertestuale Select (Seleziona) che invia l'ID del corso selezionato al metodo di azione Index.

Aggiornare la pagina e selezionare un insegnante. È ora possibile vedere una griglia con i corsi assegnati all'insegnante selezionato. Per ogni corso è possibile vedere il nome del dipartimento assegnato.

Instructors Index page instructor selected

Dopo il blocco di codice appena aggiunto, aggiungere il codice seguente. Quando è selezionato un corso, questo codice visualizza l'elenco degli studenti iscritti al corso selezionato.

@if (Model.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.Enrollments)
        {
            <tr>
                <td>
                    @item.Student.FullName
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Grade)
                </td>
            </tr>
        }
    </table>
}

Questo codice legge la Enrollments proprietà del modello di visualizzazione per visualizzare un elenco di studenti iscritti al corso.

Aggiornare di nuovo la pagina e selezionare un insegnante. Selezionare quindi un corso per visualizzare l'elenco degli studenti iscritti e i voti corrispondenti.

Instructors Index page instructor and course selected

Informazioni sul caricamento esplicito

Quando è stato recuperato l'elenco di istruttori in InstructorsController.cs, è stato specificato il caricamento eager per la CourseAssignments proprietà di navigazione.

Si supponga che gli utenti vogliano visualizzare solo raramente le iscrizioni per un corso e un insegnante selezionati. In tal caso, è consigliabile caricare i dati delle iscrizioni solo se richiesti. Per un esempio di come eseguire il caricamento esplicito, sostituire il Index metodo con il codice seguente, che rimuove il caricamento eager per Enrollments e carica tale proprietà in modo esplicito. Le modifiche al codice sono evidenziate.

public async Task<IActionResult> Index(int? id, int? courseID)
{
    var viewModel = new InstructorIndexData();
    viewModel.Instructors = await _context.Instructors
          .Include(i => i.OfficeAssignment)
          .Include(i => i.CourseAssignments)
            .ThenInclude(i => i.Course)
                .ThenInclude(i => i.Department)
          .OrderBy(i => i.LastName)
          .ToListAsync();

    if (id != null)
    {
        ViewData["InstructorID"] = id.Value;
        Instructor instructor = viewModel.Instructors.Where(
            i => i.ID == id.Value).Single();
        viewModel.Courses = instructor.CourseAssignments.Select(s => s.Course);
    }

    if (courseID != null)
    {
        ViewData["CourseID"] = courseID.Value;
        var selectedCourse = viewModel.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();
        }
        viewModel.Enrollments = selectedCourse.Enrollments;
    }

    return View(viewModel);
}

Il nuovo codice elimina le chiamate al ThenInclude metodo per i dati di registrazione dal codice che recupera le entità dell'insegnante. Elimina anche AsNoTracking. Se viene selezionato un insegnante e un corso, il codice evidenziato recupera Enrollment le entità per il corso selezionato e Student le entità per ogni Enrollmentoggetto .

Eseguire l'app e passare alla pagina di indice degli insegnanti. Non si noterà alcuna differenza in ciò che viene visualizzato nella pagina, anche se è stata modificata la modalità di recupero dei dati.

Ottenere il codice

Scaricare o visualizzare l'applicazione completata.

Passaggi successivi

In questa esercitazione:

  • Come caricare i dati correlati
  • Creazione di una pagina Courses
  • Creazione di una pagina Instructors
  • Raccolta di informazioni sul caricamento esplicito

Passare all'esercitazione successiva per informazioni su come aggiornare i dati correlati.