Część 6, Razor strony z EF Core w ASP.NET Core — odczytywanie powiązanych danych

Przez Tom Dykstra, Jon P Smith i Rick Anderson

Aplikacja internetowa Contoso University pokazuje, jak tworzyć Razor aplikacje internetowe stron przy użyciu programu EF Core Visual Studio. Aby uzyskać informacje na temat serii samouczków, zobacz pierwszy samouczek.

Jeśli napotkasz problemy, których nie możesz rozwiązać, pobierz ukończoną aplikację i porównaj ten kod z utworzonymi elementami, wykonując czynności opisane w samouczku.

W tym samouczku pokazano, jak odczytywać i wyświetlać powiązane dane. Powiązane dane to dane ładowane EF Core do właściwości nawigacji.

Na poniższych ilustracjach przedstawiono ukończone strony dla tego samouczka:

Courses Index page

Instructors Index page

Chętny, wyraźny i leniwy ładowanie

Istnieje kilka sposobów EF Core ładowania powiązanych danych do właściwości nawigacji jednostki:

  • Chętny do ładowania. Chętne ładowanie polega na tym, że zapytanie dla jednego typu jednostki również ładuje powiązane jednostki. Gdy jednostka jest odczytywana, pobierane są powiązane z nią dane. Zazwyczaj powoduje to utworzenie pojedynczego zapytania sprzężenia, które pobiera wszystkie potrzebne dane. EF Core Będzie wysyłać wiele zapytań dla niektórych typów chętnego ładowania. Wykonywanie wielu zapytań może być bardziej wydajne niż duże pojedyncze zapytanie. Wczytywanie ładowania jest określane za pomocą Include metod i ThenInclude .

    Eager loading example

    Chętne ładowanie wysyła wiele zapytań, gdy jest uwzględniona nawigacja kolekcji:

    • Jedno zapytanie dla zapytania głównego
    • Jedno zapytanie dla każdej kolekcji "edge" w drzewie ładowania.
  • Oddzielne zapytania: Loaddane można pobierać w oddzielnych zapytaniach i EF Core "naprawiać" właściwości nawigacji. "Poprawki" oznacza, że EF Core automatycznie wypełnia właściwości nawigacji. Oddzielne zapytania z funkcją Load są bardziej podobne do ładowania jawnego niż ładowanie chętne.

    Separate queries example

    Uwaga:EF Core automatycznie naprawia właściwości nawigacji do innych jednostek, które zostały wcześniej załadowane do wystąpienia kontekstu. Nawet jeśli dane właściwości nawigacji niejawnie dołączane, właściwość może być nadal wypełniana, jeśli niektóre lub wszystkie powiązane jednostki zostały wcześniej załadowane.

  • Jawne ładowanie. Gdy jednostka jest najpierw odczytywana, powiązane dane nie są pobierane. Kod musi zostać zapisany w celu pobrania powiązanych danych, gdy są potrzebne. Jawne ładowanie z oddzielnymi zapytaniami powoduje wysłanie wielu zapytań do bazy danych. W przypadku jawnego ładowania kod określa właściwości nawigacji do załadowania. Load Użyj metody , aby wykonać jawne ładowanie. Przykład:

    Explicit loading example

  • Ładowanie leniwe. Gdy jednostka jest najpierw odczytywana, powiązane dane nie są pobierane. Przy pierwszym uzyskiwaniu dostępu do właściwości nawigacji dane wymagane dla tej właściwości nawigacji są pobierane automatycznie. Zapytanie jest wysyłane do bazy danych za każdym razem, gdy właściwość nawigacji jest uzyskiwana po raz pierwszy. Ładowanie z opóźnieniem może zaszkodzić wydajności, na przykład gdy deweloperzy używają zapytań N+1. Zapytania N+1 ładują element nadrzędny i wyliczają za pośrednictwem elementów podrzędnych.

Tworzenie stron kursu

Jednostka Course zawiera właściwość nawigacji zawierającą powiązaną Department jednostkę.

Course.Department

Aby wyświetlić nazwę przypisanego działu dla kursu:

  • Załaduj powiązaną DepartmentCourse.Department jednostkę do właściwości nawigacji.
  • Pobierz nazwę z Department właściwości jednostki Name .

Strony kursu szkieletu

  • Postępuj zgodnie z instrukcjami na stronach Szkielet studenta z następującymi wyjątkami:

    • Utwórz folder Pages/Courses.
    • Użyj dla Course klasy modelu.
    • Użyj istniejącej klasy kontekstu zamiast utworzyć nową.
  • Otwórz Pages/Courses/Index.cshtml.cs i sprawdź metodę OnGetAsync . Aparat tworzenia szkieletów określił chętne Department ładowanie dla właściwości nawigacji. Metoda Include określa chętne ładowanie.

  • Uruchom aplikację i wybierz link Kursy . W kolumnie działu jest wyświetlana DepartmentIDwartość , która nie jest przydatna.

Wyświetlanie nazwy działu

Zaktualizuj plik Pages/Courses/Index.cshtml.cs przy użyciu następującego kodu:

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();
        }
    }
}

Powyższy kod zmienia Course właściwość na Courses i dodaje AsNoTrackingelement .

Zapytania śledzenia nie są przydatne, gdy wyniki są używane w scenariuszu tylko do odczytu. Zazwyczaj są one szybsze do wykonania, ponieważ nie ma potrzeby konfigurowania informacji śledzenia zmian. Jeśli jednostki pobrane z bazy danych nie muszą być aktualizowane, zapytanie śledzenia prawdopodobnie będzie działać lepiej niż zapytanie śledzenia.

W niektórych przypadkach zapytanie śledzenia jest bardziej wydajne niż zapytanie bez śledzenia. Aby uzyskać więcej informacji, zobacz Śledzenie a zapytania bez śledzenia. W poprzednim kodzie jest wywoływana, AsNoTracking ponieważ jednostki nie są aktualizowane w bieżącym kontekście.

Zaktualizuj Pages/Courses/Index.cshtml za pomocą następującego kodu.

@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>

Następujące zmiany zostały wprowadzone w kodzie szkieletowym:

  • Zmieniono Course nazwę właściwości na Courses.

  • Dodano kolumnę Number (Liczba ), która pokazuje CourseID wartość właściwości. Domyślnie klucze podstawowe nie są szkieletowe, ponieważ zwykle są one bez znaczenia dla użytkowników końcowych. Jednak w tym przypadku klucz podstawowy ma znaczenie.

  • Zmieniono kolumnę Department (Dział), aby wyświetlić nazwę działu. Kod wyświetla Name właściwość Department jednostki załadowanej do Department właściwości nawigacji:

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

Uruchom aplikację i wybierz kartę Kursy , aby wyświetlić listę z nazwami działów.

Courses Index page

Metoda OnGetAsync ładuje powiązane dane z Include metodą . Metoda Select jest alternatywą, która ładuje tylko potrzebne powiązane dane. W przypadku pojedynczych elementów, takich jak Department.Name element , używa elementu SQL INNER JOIN. W przypadku kolekcji używa innego dostępu do bazy danych, ale w Include tym celu operator kolekcji.

Poniższy kod ładuje powiązane dane z Select metodą :

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();
}

Powyższy kod nie zwraca żadnych typów jednostek, dlatego żadne śledzenie nie jest wykonywane. Aby uzyskać więcej informacji na temat śledzenia ef, zobacz Śledzenie a zapytania bez śledzenia.

Element CourseViewModel:

public class CourseViewModel
{
    public int CourseID { get; set; }
    public string Title { get; set; }
    public int Credits { get; set; }
    public string DepartmentName { get; set; }
}

Zobacz IndexSelectModel , aby uzyskać pełną Razor stronę.

Tworzenie stron instruktora

Ta sekcja zawiera strony instruktora i dodaje powiązane kursy i rejestracje do strony Indeks instruktorów.

Instructors Index page

Ta strona odczytuje i wyświetla powiązane dane w następujący sposób:

  • Lista instruktorów zawiera powiązane dane z OfficeAssignment jednostki (pakiet Office na powyższym obrazie). Jednostki Instructor i OfficeAssignment znajdują się w relacji jeden do zera lub jednego. Chętne OfficeAssignment ładowanie jest używane dla jednostek. Chętne ładowanie jest zwykle bardziej wydajne, gdy powiązane dane muszą być wyświetlane. W takim przypadku wyświetlane są przydziały biurowe dla instruktorów.
  • Gdy użytkownik wybierze instruktora, zostaną wyświetlone powiązane Course jednostki. Jednostki Instructor i Course znajdują się w relacji wiele do wielu. Chętne Course ładowanie jest używane dla jednostek i ich powiązanych Department jednostek. W takim przypadku oddzielne zapytania mogą być bardziej wydajne, ponieważ potrzebne są tylko kursy dla wybranego instruktora. W tym przykładzie pokazano, jak używać chętnego ładowania do właściwości nawigacji w jednostkach, które znajdują się we właściwościach nawigacji.
  • Gdy użytkownik wybierze kurs, zostaną wyświetlone powiązane dane z Enrollments jednostki. Na powyższym obrazie wyświetlane są imię i nazwisko ucznia oraz ocena. Jednostki Course i Enrollment znajdują się w relacji jeden do wielu.

Tworzenie modelu widoku

Na stronie instruktorów są wyświetlane dane z trzech różnych tabel. Potrzebny jest model widoku, który zawiera trzy właściwości reprezentujące trzy tabele.

Utwórz Models/SchoolViewModels/InstructorIndexData.cs za pomocą następującego kodu:

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; }
    }
}

Strony instruktora szkieletu

  • Postępuj zgodnie z instrukcjami w sekcji Tworzenie szkieletu stron uczniów z następującymi wyjątkami:

    • Utwórz folder Pages/Instructors.
    • Użyj dla Instructor klasy modelu.
    • Użyj istniejącej klasy kontekstu zamiast utworzyć nową.

Uruchom aplikację i przejdź do strony Instruktorzy.

Zaktualizuj Pages/Instructors/Index.cshtml.cs za pomocą następującego kodu:

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;
            }
        }
    }
}

Metoda OnGetAsync akceptuje opcjonalne dane trasy dla identyfikatora wybranego instruktora.

Sprawdź zapytanie w Pages/Instructors/Index.cshtml.cs pliku:

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();

Kod określa chętne ładowanie dla następujących właściwości nawigacji:

  • Instructor.OfficeAssignment
  • Instructor.Courses
    • Course.Department

Poniższy kod jest wykonywany po wybraniu instruktora, id != nullczyli .

if (id != null)
{
    InstructorID = id.Value;
    Instructor instructor = InstructorData.Instructors
        .Where(i => i.ID == id.Value).Single();
    InstructorData.Courses = instructor.Courses;
}

Wybrany instruktor jest pobierany z listy instruktorów w modelu widoku. Właściwość modelu Courses widoku jest ładowana z jednostkami Course z właściwości nawigacji wybranego instruktora Courses .

Metoda Where zwraca kolekcję. W takim przypadku filtr wybiera pojedynczą jednostkę, więc Single metoda jest wywoływana w celu przekonwertowania kolekcji na pojedynczą Instructor jednostkę. Jednostka Instructor zapewnia dostęp do Course właściwości nawigacji.

Metoda Single jest używana w kolekcji, gdy kolekcja ma tylko jeden element. Metoda Single zgłasza wyjątek, jeśli kolekcja jest pusta lub jeśli istnieje więcej niż jeden element. Alternatywą jest SingleOrDefault, która zwraca wartość domyślną, jeśli kolekcja jest pusta. Dla tego zapytania null w zwracanym domyślnym elemecie .

Poniższy kod wypełnia właściwość modelu Enrollments widoku po wybraniu kursu:

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;
}

Aktualizowanie strony indeksu instruktorów

Zaktualizuj Pages/Instructors/Index.cshtml za pomocą następującego kodu.

@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>
}

Powyższy kod wprowadza następujące zmiany:

  • page Aktualizacje dyrektywy na @page "{id:int?}". "{id:int?}" jest szablonem trasy. Szablon trasy zmienia ciągi zapytań całkowitych w adresie URL w celu kierowania danych. Na przykład kliknięcie linku Wybierz instruktora z tylko @page dyrektywą tworzy adres URL podobny do następującego:

    https://localhost:5001/Instructors?id=2

    Gdy dyrektywa page to @page "{id:int?}", adres URL to: https://localhost:5001/Instructors/2

  • Dodaje kolumnę pakietu Office , która jest wyświetlana item.OfficeAssignment.Location tylko wtedy, gdy item.OfficeAssignment nie ma wartości null. Ponieważ jest to relacja jeden do zera lub jednego, może nie być powiązana jednostka OfficeAssignment.

    @if (item.OfficeAssignment != null)
    {
        @item.OfficeAssignment.Location
    }
    
  • Dodaje kolumnę Kursy , która wyświetla kursy nauczane przez każdego instruktora. Zobacz Jawne przejście wiersza, aby uzyskać więcej informacji na temat tej składni razor.

  • Dodaje kod, który dynamicznie dodaje class="table-success" element tr wybranego instruktora i kursu. Spowoduje to ustawienie koloru tła dla wybranego wiersza przy użyciu klasy Bootstrap.

    string selectedRow = "";
    if (item.CourseID == Model.CourseID)
    {
        selectedRow = "table-success";
    }
    <tr class="@selectedRow">
    
  • Dodaje nowe hiperłącze oznaczone etykietą Wybierz. Ten link wysyła identyfikator wybranego instruktora do Index metody i ustawia kolor tła.

    <a asp-action="Index" asp-route-id="@item.ID">Select</a> |
    
  • Dodaje tabelę kursów dla wybranego instruktora.

  • Dodaje tabelę rejestracji uczniów dla wybranego kursu.

Uruchom aplikację i wybierz kartę Instruktorzy . Na stronie zostanie wyświetlony Location element (office) z powiązanej OfficeAssignment jednostki. Jeśli OfficeAssignment ma wartość null, zostanie wyświetlona pusta komórka tabeli.

Kliknij link Wybierz dla instruktora. Styl wiersza zmienia się i są wyświetlane kursy przypisane do tego instruktora.

Wybierz kurs, aby wyświetlić listę zarejestrowanych uczniów i ich ocen.

Instructors Index page instructor and course selected

Następne kroki

W następnym samouczku pokazano, jak zaktualizować powiązane dane.

W tym samouczku pokazano, jak odczytywać i wyświetlać powiązane dane. Powiązane dane to dane ładowane EF Core do właściwości nawigacji.

Na poniższych ilustracjach przedstawiono ukończone strony dla tego samouczka:

Courses Index page

Instructors Index page

Chętny, wyraźny i leniwy ładowanie

Istnieje kilka sposobów EF Core ładowania powiązanych danych do właściwości nawigacji jednostki:

  • Chętny do ładowania. Chętne ładowanie polega na tym, że zapytanie dla jednego typu jednostki również ładuje powiązane jednostki. Gdy jednostka jest odczytywana, pobierane są powiązane z nią dane. Zazwyczaj powoduje to utworzenie pojedynczego zapytania sprzężenia, które pobiera wszystkie potrzebne dane. EF Core Będzie wysyłać wiele zapytań dla niektórych typów chętnego ładowania. Wykonywanie wielu zapytań może być bardziej wydajne niż gigantyczne pojedyncze zapytanie. Wczytywanie ładowania jest określane za pomocą Include metod i ThenInclude .

    Eager loading example

    Chętne ładowanie wysyła wiele zapytań, gdy jest uwzględniona nawigacja kolekcji:

    • Jedno zapytanie dla zapytania głównego
    • Jedno zapytanie dla każdej kolekcji "edge" w drzewie ładowania.
  • Oddzielne zapytania: Loaddane można pobierać w oddzielnych zapytaniach i EF Core "naprawiać" właściwości nawigacji. "Poprawki" oznacza, że EF Core automatycznie wypełnia właściwości nawigacji. Oddzielne zapytania z funkcją Load są bardziej podobne do ładowania jawnego niż ładowanie chętne.

    Separate queries example

    Uwaga:EF Core automatycznie naprawia właściwości nawigacji do innych jednostek, które zostały wcześniej załadowane do wystąpienia kontekstu. Nawet jeśli dane właściwości nawigacji niejawnie dołączane, właściwość może być nadal wypełniana, jeśli niektóre lub wszystkie powiązane jednostki zostały wcześniej załadowane.

  • Jawne ładowanie. Gdy jednostka jest najpierw odczytywana, powiązane dane nie są pobierane. Kod musi zostać zapisany w celu pobrania powiązanych danych, gdy są potrzebne. Jawne ładowanie z oddzielnymi zapytaniami powoduje wysłanie wielu zapytań do bazy danych. W przypadku jawnego ładowania kod określa właściwości nawigacji do załadowania. Load Użyj metody , aby wykonać jawne ładowanie. Przykład:

    Explicit loading example

  • Ładowanie leniwe. Gdy jednostka jest najpierw odczytywana, powiązane dane nie są pobierane. Przy pierwszym uzyskiwaniu dostępu do właściwości nawigacji dane wymagane dla tej właściwości nawigacji są pobierane automatycznie. Zapytanie jest wysyłane do bazy danych za każdym razem, gdy właściwość nawigacji jest uzyskiwana po raz pierwszy. Ładowanie leniwe może zaszkodzić wydajności, na przykład gdy deweloperzy używają wzorców N+1, ładując element nadrzędny i wyliczając za pośrednictwem elementów podrzędnych.

Tworzenie stron kursu

Jednostka Course zawiera właściwość nawigacji zawierającą powiązaną Department jednostkę.

Course.Department

Aby wyświetlić nazwę przypisanego działu dla kursu:

  • Załaduj powiązaną DepartmentCourse.Department jednostkę do właściwości nawigacji.
  • Pobierz nazwę z Department właściwości jednostki Name .

Strony kursu szkieletu

  • Postępuj zgodnie z instrukcjami na stronach Szkielet studenta z następującymi wyjątkami:

    • Utwórz folder Pages/Courses.
    • Użyj dla Course klasy modelu.
    • Użyj istniejącej klasy kontekstu zamiast utworzyć nową.
  • Otwórz Pages/Courses/Index.cshtml.cs i sprawdź metodę OnGetAsync . Aparat tworzenia szkieletów określił chętne Department ładowanie dla właściwości nawigacji. Metoda Include określa chętne ładowanie.

  • Uruchom aplikację i wybierz link Kursy . W kolumnie działu jest wyświetlana DepartmentIDwartość , która nie jest przydatna.

Wyświetlanie nazwy działu

Zaktualizuj plik Pages/Courses/Index.cshtml.cs przy użyciu następującego kodu:

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();
        }
    }
}

Powyższy kod zmienia Course właściwość na Courses i dodaje AsNoTrackingelement . AsNoTracking poprawia wydajność, ponieważ zwracane jednostki nie są śledzone. Jednostki nie muszą być śledzone, ponieważ nie są aktualizowane w bieżącym kontekście.

Zaktualizuj Pages/Courses/Index.cshtml za pomocą następującego kodu.

@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>

Następujące zmiany zostały wprowadzone w kodzie szkieletowym:

  • Zmieniono Course nazwę właściwości na Courses.

  • Dodano kolumnę Number (Liczba ), która pokazuje CourseID wartość właściwości. Domyślnie klucze podstawowe nie są szkieletowe, ponieważ zwykle są one bez znaczenia dla użytkowników końcowych. Jednak w tym przypadku klucz podstawowy ma znaczenie.

  • Zmieniono kolumnę Department (Dział), aby wyświetlić nazwę działu. Kod wyświetla Name właściwość Department jednostki załadowanej do Department właściwości nawigacji:

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

Uruchom aplikację i wybierz kartę Kursy , aby wyświetlić listę z nazwami działów.

Courses Index page

Metoda OnGetAsync ładuje powiązane dane z Include metodą . Metoda Select jest alternatywą, która ładuje tylko potrzebne powiązane dane. W przypadku pojedynczych elementów, takich jak Department.Name używane jest sprzężenie WEWNĘTRZNE SQL. W przypadku kolekcji używa innego dostępu do bazy danych, ale w Include tym celu operator kolekcji.

Poniższy kod ładuje powiązane dane z Select metodą :

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();
}

Powyższy kod nie zwraca żadnych typów jednostek, dlatego żadne śledzenie nie jest wykonywane. Aby uzyskać więcej informacji na temat śledzenia ef, zobacz Śledzenie a zapytania bez śledzenia.

Element CourseViewModel:

public class CourseViewModel
{
    public int CourseID { get; set; }
    public string Title { get; set; }
    public int Credits { get; set; }
    public string DepartmentName { get; set; }
}

Zobacz IndexSelect.cshtml i IndexSelect.cshtml.cs , aby zapoznać się z kompletnym przykładem.

Tworzenie stron instruktora

Ta sekcja zawiera strony instruktora i dodaje powiązane kursy i rejestracje do strony Indeks instruktorów.

Instructors Index page

Ta strona odczytuje i wyświetla powiązane dane w następujący sposób:

  • Lista instruktorów zawiera powiązane dane z OfficeAssignment jednostki (pakiet Office na powyższym obrazie). Jednostki Instructor i OfficeAssignment znajdują się w relacji jeden do zera lub jednego. Chętne OfficeAssignment ładowanie jest używane dla jednostek. Chętne ładowanie jest zwykle bardziej wydajne, gdy powiązane dane muszą być wyświetlane. W takim przypadku wyświetlane są przydziały biurowe dla instruktorów.
  • Gdy użytkownik wybierze instruktora, zostaną wyświetlone powiązane Course jednostki. Jednostki Instructor i Course znajdują się w relacji wiele do wielu. Chętne Course ładowanie jest używane dla jednostek i ich powiązanych Department jednostek. W takim przypadku oddzielne zapytania mogą być bardziej wydajne, ponieważ potrzebne są tylko kursy dla wybranego instruktora. W tym przykładzie pokazano, jak używać chętnego ładowania do właściwości nawigacji w jednostkach, które znajdują się we właściwościach nawigacji.
  • Gdy użytkownik wybierze kurs, zostaną wyświetlone powiązane dane z Enrollments jednostki. Na powyższym obrazie wyświetlane są imię i nazwisko ucznia oraz ocena. Jednostki Course i Enrollment znajdują się w relacji jeden do wielu.

Tworzenie modelu widoku

Na stronie instruktorów są wyświetlane dane z trzech różnych tabel. Potrzebny jest model widoku, który zawiera trzy właściwości reprezentujące trzy tabele.

Utwórz SchoolViewModels/InstructorIndexData.cs za pomocą następującego kodu:

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; }
    }
}

Strony instruktora szkieletu

  • Postępuj zgodnie z instrukcjami w sekcji Tworzenie szkieletu stron uczniów z następującymi wyjątkami:

    • Utwórz folder Pages/Instructors.
    • Użyj dla Instructor klasy modelu.
    • Użyj istniejącej klasy kontekstu zamiast utworzyć nową.

Aby zobaczyć, jak wygląda strona szkieletowa przed jej zaktualizowaniem, uruchom aplikację i przejdź do strony Instruktorzy.

Zaktualizuj Pages/Instructors/Index.cshtml.cs za pomocą następującego kodu:

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;
            }
        }
    }
}

Metoda OnGetAsync akceptuje opcjonalne dane trasy dla identyfikatora wybranego instruktora.

Sprawdź zapytanie w Pages/Instructors/Index.cshtml.cs pliku:

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();

Kod określa chętne ładowanie dla następujących właściwości nawigacji:

  • Instructor.OfficeAssignment
  • Instructor.CourseAssignments
    • CourseAssignments.Course
      • Course.Department
      • Course.Enrollments
        • Enrollment.Student

Zwróć uwagę na powtórzenie Include metod i ThenInclude dla CourseAssignments i Course. To powtórzenie jest niezbędne do określenia chętnego Course ładowania dla dwóch właściwości nawigacji jednostki.

Poniższy kod jest wykonywany po wybraniu instruktora (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);
}

Wybrany instruktor jest pobierany z listy instruktorów w modelu widoku. Właściwość modelu Courses widoku jest ładowana z jednostkami Course z tej właściwości nawigacji instruktora CourseAssignments .

Metoda Where zwraca kolekcję. Jednak w tym przypadku filtr wybierze jedną jednostkę, więc Single metoda jest wywoływana w celu przekonwertowania kolekcji na pojedynczą Instructor jednostkę. Jednostka Instructor zapewnia dostęp do CourseAssignments właściwości . CourseAssignments zapewnia dostęp do powiązanych Course jednostek.

Instructor-to-Courses m:M

Metoda Single jest używana w kolekcji, gdy kolekcja ma tylko jeden element. Metoda Single zgłasza wyjątek, jeśli kolekcja jest pusta lub jeśli istnieje więcej niż jeden element. Alternatywą jest SingleOrDefault, która zwraca wartość domyślną (null w tym przypadku), jeśli kolekcja jest pusta.

Poniższy kod wypełnia właściwość modelu Enrollments widoku po wybraniu kursu:

if (courseID != null)
{
    CourseID = courseID.Value;
    var selectedCourse = InstructorData.Courses
        .Where(x => x.CourseID == courseID).Single();
    InstructorData.Enrollments = selectedCourse.Enrollments;
}

Aktualizowanie strony indeksu instruktorów

Zaktualizuj Pages/Instructors/Index.cshtml za pomocą następującego kodu.

@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>
}

Powyższy kod wprowadza następujące zmiany:

  • page Aktualizacje dyrektywy od @page do @page "{id:int?}". "{id:int?}" jest szablonem trasy. Szablon trasy zmienia ciągi zapytań całkowitych w adresie URL w celu kierowania danych. Na przykład kliknięcie linku Wybierz instruktora z tylko @page dyrektywą tworzy adres URL podobny do następującego:

    https://localhost:5001/Instructors?id=2

    Gdy dyrektywa page to @page "{id:int?}", adres URL to:

    https://localhost:5001/Instructors/2

  • Dodaje kolumnę pakietu Office , która jest wyświetlana item.OfficeAssignment.Location tylko wtedy, gdy item.OfficeAssignment nie ma wartości null. Ponieważ jest to relacja jeden do zera lub jednego, może nie być powiązana jednostka OfficeAssignment.

    @if (item.OfficeAssignment != null)
    {
        @item.OfficeAssignment.Location
    }
    
  • Dodaje kolumnę Kursy , która wyświetla kursy nauczane przez każdego instruktora. Zobacz Jawne przejście wiersza, aby uzyskać więcej informacji na temat tej składni razor.

  • Dodaje kod, który dynamicznie dodaje class="table-success" element tr wybranego instruktora i kursu. Spowoduje to ustawienie koloru tła dla wybranego wiersza przy użyciu klasy Bootstrap.

    string selectedRow = "";
    if (item.CourseID == Model.CourseID)
    {
        selectedRow = "table-success";
    }
    <tr class="@selectedRow">
    
  • Dodaje nowe hiperłącze oznaczone etykietą Wybierz. Ten link wysyła identyfikator wybranego instruktora do Index metody i ustawia kolor tła.

    <a asp-action="Index" asp-route-id="@item.ID">Select</a> |
    
  • Dodaje tabelę kursów dla wybranego instruktora.

  • Dodaje tabelę rejestracji uczniów dla wybranego kursu.

Uruchom aplikację i wybierz kartę Instruktorzy . Na stronie zostanie wyświetlony Location element (office) z powiązanej OfficeAssignment jednostki. Jeśli OfficeAssignment ma wartość null, zostanie wyświetlona pusta komórka tabeli.

Kliknij link Wybierz dla instruktora. Styl wiersza zmienia się i są wyświetlane kursy przypisane do tego instruktora.

Wybierz kurs, aby wyświetlić listę zarejestrowanych uczniów i ich ocen.

Instructors Index page instructor and course selected

Korzystanie z pojedynczego

Metoda Single może przekazać Where warunek zamiast wywoływać metodę Where oddzielnie:

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;
    }
}

Używanie z Single warunkiem Where jest kwestią osobistych preferencji. Nie zapewnia żadnych korzyści z używania Where metody .

Jawne ładowanie

Bieżący kod określa chętne ładowanie dla Enrollments i 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();

Załóżmy, że użytkownicy rzadko chcą widzieć rejestracje w ramach kursu. W takim przypadku optymalizacją byłoby załadowanie tylko danych rejestracji, jeśli jest to wymagane. W tej sekcji zaktualizowano element OnGetAsync w celu użycia jawnego ładowania elementów Enrollments i Students.

Zaktualizuj Pages/Instructors/Index.cshtml.cs za pomocą następującego kodu.

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;
            }
        }
    }
}

Powyższy kod odrzuca metodę ThenInclude dla danych rejestracji i uczniów. Jeśli wybrano kurs, zostanie pobrany jawny kod ładowania:

  • Jednostki Enrollment wybranego kursu.
  • Jednostki Student dla każdego Enrollmentelementu .

Zwróć uwagę, że poprzedni kod komentuje .AsNoTracking()element . Właściwości nawigacji można jawnie załadować tylko dla śledzonych jednostek.

Testowanie aplikacji. Z perspektywy użytkownika aplikacja zachowuje się identycznie z poprzednią wersją.

Następne kroki

W następnym samouczku pokazano, jak zaktualizować powiązane dane.

W tym samouczku powiązane dane są odczytywane i wyświetlane. Powiązane dane to dane ładowane EF Core do właściwości nawigacji.

Jeśli napotkasz problemy, nie możesz rozwiązać, pobierz lub wyświetl ukończoną aplikację.Pobierz instrukcje.

Na poniższych ilustracjach przedstawiono ukończone strony dla tego samouczka:

Courses Index page

Instructors Index page

Istnieje kilka sposobów EF Core ładowania powiązanych danych do właściwości nawigacji jednostki:

  • Chętny do ładowania. Chętne ładowanie polega na tym, że zapytanie dla jednego typu jednostki również ładuje powiązane jednostki. Gdy jednostka jest odczytywana, pobierane są powiązane z nią dane. Zazwyczaj powoduje to utworzenie pojedynczego zapytania sprzężenia, które pobiera wszystkie potrzebne dane. EF Core Będzie wysyłać wiele zapytań dla niektórych typów chętnego ładowania. Wykonywanie wielu zapytań może być bardziej wydajne niż w przypadku niektórych zapytań w programie EF6, w którym istniało pojedyncze zapytanie. Wczytywanie ładowania jest określane za pomocą Include metod i ThenInclude .

    Eager loading example

    Chętne ładowanie wysyła wiele zapytań, gdy jest uwzględniona nawigacja kolekcji:

    • Jedno zapytanie dla zapytania głównego
    • Jedno zapytanie dla każdej kolekcji "edge" w drzewie ładowania.
  • Oddzielne zapytania: Loaddane można pobierać w oddzielnych zapytaniach i EF Core "naprawiać" właściwości nawigacji. "poprawki" oznacza, że EF Core automatycznie wypełnia właściwości nawigacji. Oddzielne zapytania z funkcją Load są bardziej podobne do ładowania jawnego niż ładowanie chętne.

    Separate queries example

    Uwaga: EF Core automatycznie naprawia właściwości nawigacji do innych jednostek, które zostały wcześniej załadowane do wystąpienia kontekstu. Nawet jeśli dane właściwości nawigacji niejawnie dołączane, właściwość może być nadal wypełniana, jeśli niektóre lub wszystkie powiązane jednostki zostały wcześniej załadowane.

  • Jawne ładowanie. Gdy jednostka jest najpierw odczytywana, powiązane dane nie są pobierane. Kod musi zostać zapisany w celu pobrania powiązanych danych, gdy są potrzebne. Jawne ładowanie z oddzielnymi zapytaniami powoduje wysłanie wielu zapytań do bazy danych. W przypadku jawnego ładowania kod określa właściwości nawigacji do załadowania. Load Użyj metody , aby wykonać jawne ładowanie. Przykład:

    Explicit loading example

  • Ładowanie leniwe. Ładowanie z opóźnieniem zostało dodane do EF Core wersji 2.1. Gdy jednostka jest najpierw odczytywana, powiązane dane nie są pobierane. Przy pierwszym uzyskiwaniu dostępu do właściwości nawigacji dane wymagane dla tej właściwości nawigacji są pobierane automatycznie. Zapytanie jest wysyłane do bazy danych za każdym razem, gdy właściwość nawigacji jest uzyskiwana po raz pierwszy.

  • Operator Select ładuje tylko potrzebne powiązane dane.

Tworzenie strony Kursu z wyświetloną nazwą działu

Jednostka Course zawiera właściwość nawigacji zawierającą Department jednostkę. Jednostka Department zawiera dział, do którego przypisano kurs.

Aby wyświetlić nazwę przypisanego działu na liście kursów:

  • Name Pobierz właściwość z Department jednostki.
  • Jednostka Department pochodzi z Course.Department właściwości nawigacji.

Course.Department

Tworzenie szkieletu modelu kursu

Postępuj zgodnie z instrukcjami w artykule Tworzenie szkieletu modelu ucznia i używanie go Course do klasy modelu.

Poprzednie polecenie szkieletuje Course model. Otwórz projekt w programie Visual Studio.

Otwórz Pages/Courses/Index.cshtml.cs i sprawdź metodę OnGetAsync . Aparat tworzenia szkieletów określił chętne Department ładowanie dla właściwości nawigacji. Metoda Include określa chętne ładowanie.

Uruchom aplikację i wybierz link Kursy . W kolumnie działu jest wyświetlana DepartmentIDwartość , która nie jest przydatna.

Zaktualizuj metodę OnGetAsync przy użyciu następującego kodu:

public async Task OnGetAsync()
{
    Course = await _context.Courses
        .Include(c => c.Department)
        .AsNoTracking()
        .ToListAsync();
}

Powyższy kod dodaje AsNoTrackingelement . AsNoTracking poprawia wydajność, ponieważ zwracane jednostki nie są śledzone. Jednostki nie są śledzone, ponieważ nie są aktualizowane w bieżącym kontekście.

Zaktualizuj Pages/Courses/Index.cshtml za pomocą następującego wyróżnionego znacznika:

@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>

Następujące zmiany zostały wprowadzone w kodzie szkieletowym:

  • Zmieniono nagłówek z Indeks na Kursy.

  • Dodano kolumnę Number (Liczba ), która pokazuje CourseID wartość właściwości. Domyślnie klucze podstawowe nie są szkieletowe, ponieważ zwykle są one bez znaczenia dla użytkowników końcowych. Jednak w tym przypadku klucz podstawowy ma znaczenie.

  • Zmieniono kolumnę Department (Dział), aby wyświetlić nazwę działu. Kod wyświetla Name właściwość Department jednostki załadowanej do Department właściwości nawigacji:

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

Uruchom aplikację i wybierz kartę Kursy , aby wyświetlić listę z nazwami działów.

Courses Index page

Metoda OnGetAsync ładuje powiązane dane z Include metodą :

public async Task OnGetAsync()
{
    Course = await _context.Courses
        .Include(c => c.Department)
        .AsNoTracking()
        .ToListAsync();
}

Operator Select ładuje tylko potrzebne powiązane dane. W przypadku pojedynczych elementów, takich jak Department.Name używane jest sprzężenie WEWNĘTRZNE SQL. W przypadku kolekcji używa innego dostępu do bazy danych, ale w Include tym celu operator kolekcji.

Poniższy kod ładuje powiązane dane z Select metodą :

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();
}

Element CourseViewModel:

public class CourseViewModel
{
    public int CourseID { get; set; }
    public string Title { get; set; }
    public int Credits { get; set; }
    public string DepartmentName { get; set; }
}

Zobacz IndexSelect.cshtml i IndexSelect.cshtml.cs , aby zapoznać się z kompletnym przykładem.

Tworzenie strony Instruktorzy z wyświetlonymi kursami i rejestracjami

W tej sekcji zostanie utworzona strona Instruktorzy.

Instructors Index page

Ta strona odczytuje i wyświetla powiązane dane w następujący sposób:

  • Lista instruktorów zawiera powiązane dane z OfficeAssignment jednostki (pakiet Office na powyższym obrazie). Jednostki Instructor i OfficeAssignment znajdują się w relacji jeden do zera lub jednego. Chętne OfficeAssignment ładowanie jest używane dla jednostek. Chętne ładowanie jest zwykle bardziej wydajne, gdy powiązane dane muszą być wyświetlane. W takim przypadku wyświetlane są przydziały biurowe dla instruktorów.
  • Gdy użytkownik wybierze instruktora (Harui na powyższym obrazie), zostaną wyświetlone powiązane Course jednostki. Jednostki Instructor i Course znajdują się w relacji wiele do wielu. Chętne Course ładowanie jest używane dla jednostek i ich powiązanych Department jednostek. W takim przypadku oddzielne zapytania mogą być bardziej wydajne, ponieważ potrzebne są tylko kursy dla wybranego instruktora. W tym przykładzie pokazano, jak używać chętnego ładowania do właściwości nawigacji w jednostkach, które znajdują się we właściwościach nawigacji.
  • Gdy użytkownik wybierze kurs (Chemia na powyższym obrazie), wyświetlane są powiązane dane z Enrollments jednostki. Na powyższym obrazie wyświetlane są imię i nazwisko ucznia oraz ocena. Jednostki Course i Enrollment znajdują się w relacji jeden do wielu.

Tworzenie modelu widoku dla widoku Indeks instruktora

Na stronie instruktorów są wyświetlane dane z trzech różnych tabel. Tworzony jest model widoku zawierający trzy jednostki reprezentujące trzy tabele.

W folderze SchoolViewModels utwórz InstructorIndexData.cs za pomocą następującego kodu:

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; }
    }
}

Tworzenie szkieletu modelu instruktora

Postępuj zgodnie z instrukcjami w artykule Tworzenie szkieletu modelu ucznia i używanie go Instructor do klasy modelu.

Poprzednie polecenie szkieletuje Instructor model. Uruchom aplikację i przejdź do strony instruktorów.

Zastąp Pages/Instructors/Index.cshtml.cs ciąg następującym kodem:

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;
            }           
        }
    }
}

Metoda OnGetAsync akceptuje opcjonalne dane trasy dla identyfikatora wybranego instruktora.

Sprawdź zapytanie w Pages/Instructors/Index.cshtml.cs pliku:

Instructor.Instructors = await _context.Instructors
      .Include(i => i.OfficeAssignment)
      .Include(i => i.CourseAssignments)
        .ThenInclude(i => i.Course)
      .AsNoTracking()
      .OrderBy(i => i.LastName)
      .ToListAsync();

Zapytanie ma dwa następujące elementy:

  • OfficeAssignment: wyświetlany w widoku instruktorów.
  • CourseAssignments: Co przynosi kursy nauczane.

Aktualizowanie strony indeksu instruktorów

Zaktualizuj Pages/Instructors/Index.cshtml za pomocą następującego znacznika:

@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>

Powyższy znacznik wprowadza następujące zmiany:

  • page Aktualizacje dyrektywy od @page do @page "{id:int?}". "{id:int?}" jest szablonem trasy. Szablon trasy zmienia ciągi zapytań całkowitych w adresie URL w celu kierowania danych. Na przykład kliknięcie linku Wybierz instruktora z tylko @page dyrektywą tworzy adres URL podobny do następującego:

    http://localhost:1234/Instructors?id=2

    Gdy dyrektywa page to @page "{id:int?}", poprzedni adres URL to:

    http://localhost:1234/Instructors/2

  • Tytuł strony to Instruktorzy.

  • Dodano kolumnę pakietu Office , która jest wyświetlana item.OfficeAssignment.Location tylko wtedy, gdy item.OfficeAssignment nie ma wartości null. Ponieważ jest to relacja jeden do zera lub jednego, może nie być powiązana jednostka OfficeAssignment.

    @if (item.OfficeAssignment != null)
    {
        @item.OfficeAssignment.Location
    }
    
  • Dodano kolumnę Kursy zawierającą kursy nauczane przez każdego instruktora. Zobacz Jawne przejście wiersza, aby uzyskać więcej informacji na temat tej składni razor.

  • Dodano kod, który dynamicznie dodaje class="success" element tr wybranego instruktora. Spowoduje to ustawienie koloru tła dla wybranego wiersza przy użyciu klasy Bootstrap.

    string selectedRow = "";
    if (item.CourseID == Model.CourseID)
    {
        selectedRow = "success";
    }
    <tr class="@selectedRow">
    
  • Dodano nowy hiperlink z etykietą Wybierz. Ten link wysyła identyfikator wybranego instruktora do Index metody i ustawia kolor tła.

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

Uruchom aplikację i wybierz kartę Instruktorzy . Na stronie zostanie wyświetlony Location element (office) z powiązanej OfficeAssignment jednostki. Jeśli wartość OfficeAssignment ma wartość null, zostanie wyświetlona pusta komórka tabeli.

Kliknij link Wybierz. Styl wiersza zmienia się.

Dodawanie kursów nauczanych przez wybranego instruktora

Zaktualizuj metodę w pliku OnGetAsyncPages/Instructors/Index.cshtml.cs przy użyciu następującego kodu:

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;
    }
}

Dodaj 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;
        }
    }

Sprawdź zaktualizowane zapytanie:

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();

Poprzednie zapytanie dodaje Department jednostki.

Poniższy kod jest wykonywany po wybraniu instruktora (id != null). Wybrany instruktor jest pobierany z listy instruktorów w modelu widoku. Właściwość modelu Courses widoku jest ładowana z jednostkami Course z tej właściwości nawigacji instruktora CourseAssignments .

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);
}

Metoda Where zwraca kolekcję. W poprzedniej Where metodzie zwracana jest tylko jedna Instructor jednostka. Metoda Single konwertuje kolekcję na jedną Instructor jednostkę. Jednostka Instructor zapewnia dostęp do CourseAssignments właściwości . CourseAssignments zapewnia dostęp do powiązanych Course jednostek.

Instructor-to-Courses m:M

Metoda Single jest używana w kolekcji, gdy kolekcja ma tylko jeden element. Metoda Single zgłasza wyjątek, jeśli kolekcja jest pusta lub jeśli istnieje więcej niż jeden element. Alternatywą jest SingleOrDefault, która zwraca wartość domyślną (null w tym przypadku), jeśli kolekcja jest pusta. Używanie SingleOrDefault w pustej kolekcji:

  • Powoduje wyjątek (od próby znalezienia Courses właściwości w odwołaniu o wartości null).
  • Komunikat o wyjątku mniej wyraźnie wskazuje przyczynę problemu.

Poniższy kod wypełnia właściwość modelu Enrollments widoku po wybraniu kursu:

if (courseID != null)
{
    CourseID = courseID.Value;
    Instructor.Enrollments = Instructor.Courses.Where(
        x => x.CourseID == courseID).Single().Enrollments;
}

Dodaj następujący znacznik na końcu Pages/Instructors/Index.cshtmlRazor strony:

                    <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>
}

W poprzednim znaczniku wyświetlana jest lista kursów związanych z instruktorem po wybraniu instruktora.

Testowanie aplikacji. Kliknij link Wybierz na stronie instruktorów.

Pokaż dane uczniów

W tej sekcji aplikacja zostanie zaktualizowana, aby wyświetlić dane uczniów dla wybranego kursu.

Zaktualizuj zapytanie w metodzie w Pages/Instructors/Index.cshtml.cs metodzie OnGetAsync za pomocą następującego kodu:

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();

Zaktualizuj element Pages/Instructors/Index.cshtml. Dodaj następujący znacznik na końcu pliku:


@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>
}

Powyższy znacznik wyświetla listę uczniów, którzy są zarejestrowani w wybranym kursie.

Odśwież stronę i wybierz instruktora. Wybierz kurs, aby wyświetlić listę zarejestrowanych uczniów i ich ocen.

Instructors Index page instructor and course selected

Korzystanie z pojedynczego

Metoda Single może przekazać Where warunek zamiast wywoływać metodę Where oddzielnie:

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;
    }
}

Single Powyższe podejście nie zapewnia żadnych korzyści z używania programu Where. Niektórzy deweloperzy wolą Single styl podejścia.

Jawne ładowanie

Bieżący kod określa chętne ładowanie dla Enrollments i 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();

Załóżmy, że użytkownicy rzadko chcą widzieć rejestracje w ramach kursu. W takim przypadku optymalizacją byłoby załadowanie tylko danych rejestracji, jeśli jest to wymagane. W tej sekcji zaktualizowano element OnGetAsync w celu użycia jawnego ładowania elementów Enrollments i Students.

Zaktualizuj element za OnGetAsync pomocą następującego kodu:

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;
    }
}

Powyższy kod odrzuca metodę ThenInclude dla danych rejestracji i uczniów. Jeśli wybrano kurs, wyróżniony kod pobiera:

  • Jednostki Enrollment wybranego kursu.
  • Jednostki Student dla każdego Enrollmentelementu .

Zwróć uwagę na powyższe komentarze .AsNoTracking()kodu. Właściwości nawigacji można jawnie załadować tylko dla śledzonych jednostek.

Testowanie aplikacji. Z perspektywy użytkowników aplikacja zachowuje się identycznie z poprzednią wersją.

W następnym samouczku pokazano, jak zaktualizować powiązane dane.

Dodatkowe zasoby