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:
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 .
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:
Load
dane 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.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 nie są jawnie 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. Na przykład:Ł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ę.
Aby wyświetlić nazwę przypisanego działu dla kursu:
- Załaduj powiązaną
Department
Course.Department
jednostkę do właściwości nawigacji. - Pobierz nazwę z
Department
właściwości jednostkiName
.
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ętneDepartment
ładowanie dla właściwości nawigacji. MetodaInclude
określa chętne ładowanie.Uruchom aplikację i wybierz link Kursy . W kolumnie działu jest wyświetlana
DepartmentID
wartość , która nie jest przydatna.
Wyświetlanie nazwy działu
Zaktualizuj strony/kursy/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 AsNoTracking
element .
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 naCourses
.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 doDepartment
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.
Ładowanie powiązanych danych za pomocą polecenia Select
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.
Pomocnik 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.
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). JednostkiInstructor
iOfficeAssignment
znajdują się w relacji jeden do zera lub jednego. ChętneOfficeAssignment
ł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. JednostkiInstructor
iCourse
znajdują się w relacji wiele do wielu. ChętneCourse
ładowanie jest używane dla jednostek i ich powiązanychDepartment
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. JednostkiCourse
iEnrollment
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 != null
czyli .
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:
Aktualizuje dyrektywę
page
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, gdyitem.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. Aby uzyskać więcej informacji na temat tej razor składni, zobacz Jawne przejście wiersza.
Dodaje kod, który dynamicznie dodaje
class="table-success"
elementtr
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.
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:
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 iThenInclude
.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:
Load
dane 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.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 nie są jawnie 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. Na przykład:Ł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ę.
Aby wyświetlić nazwę przypisanego działu dla kursu:
- Załaduj powiązaną
Department
Course.Department
jednostkę do właściwości nawigacji. - Pobierz nazwę z
Department
właściwości jednostkiName
.
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ętneDepartment
ładowanie dla właściwości nawigacji. MetodaInclude
określa chętne ładowanie.Uruchom aplikację i wybierz link Kursy . W kolumnie działu jest wyświetlana
DepartmentID
wartość , która nie jest przydatna.
Wyświetlanie nazwy działu
Zaktualizuj strony/kursy/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 AsNoTracking
element . 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 naCourses
.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 doDepartment
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.
Ładowanie powiązanych danych za pomocą polecenia Select
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.
Pomocnik 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.
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). JednostkiInstructor
iOfficeAssignment
znajdują się w relacji jeden do zera lub jednego. ChętneOfficeAssignment
ł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. JednostkiInstructor
iCourse
znajdują się w relacji wiele do wielu. ChętneCourse
ładowanie jest używane dla jednostek i ich powiązanychDepartment
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. JednostkiCourse
iEnrollment
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.
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:
Aktualizuje dyrektywę
page
z@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, gdyitem.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. Aby uzyskać więcej informacji na temat tej razor składni, zobacz Jawne przejście wiersza.
Dodaje kod, który dynamicznie dodaje
class="table-success"
elementtr
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.
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żdegoEnrollment
elementu .
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:
Chętne, jawne i leniwe ładowanie powiązanych danych
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 iThenInclude
.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:
Load
dane 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.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 nie są jawnie 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. Na przykład:Ł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ść zDepartment
jednostki.- Jednostka
Department
pochodzi zCourse.Department
właściwości nawigacji.
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 DepartmentID
wartość , 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 AsNoTracking
element . 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 doDepartment
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.
Ładowanie powiązanych danych za pomocą polecenia Select
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();
}
Pomocnik 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.
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). JednostkiInstructor
iOfficeAssignment
znajdują się w relacji jeden do zera lub jednego. ChętneOfficeAssignment
ł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. JednostkiInstructor
iCourse
znajdują się w relacji wiele do wielu. ChętneCourse
ładowanie jest używane dla jednostek i ich powiązanychDepartment
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. JednostkiCourse
iEnrollment
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:
Aktualizuje dyrektywę
page
z@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, gdyitem.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. Aby uzyskać więcej informacji na temat tej razor składni, zobacz Jawne przejście wiersza.
Dodano kod, który dynamicznie dodaje
class="success"
elementtr
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 OnGetAsync
Pages/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.
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.cshtml
Razor 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.
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żdegoEnrollment
elementu .
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.