Bagian 6, Razor Halaman dengan EF Core inti ASP.NET - Membaca Data Terkait
Oleh Tom Dykstra, Jon P Smith, dan Rick Anderson
Aplikasi web Contoso University menunjukkan cara membuat Razor aplikasi web Pages menggunakan EF Core dan Visual Studio. Untuk informasi tentang seri tutorial, lihat tutorial pertama.
Jika Anda mengalami masalah yang tidak dapat Anda selesaikan, unduh aplikasi yang telah selesai dan bandingkan kode tersebut dengan apa yang Anda buat dengan mengikuti tutorial.
Tutorial ini menunjukkan cara membaca dan menampilkan data terkait. Data terkait adalah data yang EF Core dimuat ke dalam properti navigasi.
Ilustrasi berikut menunjukkan halaman yang telah selesai untuk tutorial ini:
Pemuatan bersemangat, eksplisit, dan malas
Ada beberapa cara yang EF Core dapat memuat data terkait ke dalam properti navigasi entitas:
Pemuatan bersemangat. Pemuatan bersemangat adalah ketika kueri untuk satu jenis entitas juga memuat entitas terkait. Saat entitas dibaca, data terkait diambil. Ini biasanya menghasilkan satu kueri gabungan yang mengambil semua data yang diperlukan. EF Core akan mengeluarkan beberapa kueri untuk beberapa jenis pemuatan yang bersemangat. Mengeluarkan beberapa kueri bisa lebih efisien daripada kueri tunggal yang besar. Pemuatan bersemangat ditentukan dengan Include metode dan ThenInclude .
Pemuatan bersemangat mengirim beberapa kueri saat navigasi koleksi disertakan:
- Satu kueri untuk kueri utama
- Satu kueri untuk setiap koleksi "tepi" di pohon beban.
Kueri terpisah dengan
Load
: Data dapat diambil dalam kueri terpisah, dan EF Core "memperbaiki" properti navigasi. "Perbaikan" berarti secara EF Core otomatis mengisi properti navigasi. Kueri terpisah denganLoad
lebih seperti pemuatan eksplisit daripada pemuatan yang bersemangat.Catatan:EF Core secara otomatis memperbaiki properti navigasi ke entitas lain yang sebelumnya dimuat ke dalam instans konteks. Bahkan jika data untuk properti navigasi tidak disertakan secara eksplisit, properti mungkin masih diisi jika beberapa atau semua entitas terkait sebelumnya dimuat.
Pemuatan eksplisit. Saat entitas pertama kali dibaca, data terkait tidak diambil. Kode harus ditulis untuk mengambil data terkait saat diperlukan. Pemuatan eksplisit dengan kueri terpisah menghasilkan beberapa kueri yang dikirim ke database. Dengan pemuatan eksplisit, kode menentukan properti navigasi yang akan dimuat.
Load
Gunakan metode untuk melakukan pemuatan eksplisit. Contohnya:Pemuatan malas. Saat entitas pertama kali dibaca, data terkait tidak diambil. Saat pertama kali properti navigasi diakses, data yang diperlukan untuk properti navigasi tersebut diambil secara otomatis. Kueri dikirim ke database setiap kali properti navigasi diakses untuk pertama kalinya. Pemuatan malas dapat merusak performa, misalnya ketika pengembang menggunakan kueri N+1. Kueri N+1 memuat induk dan menghitung melalui turunan.
Membuat halaman Kursus
Entitas Course
menyertakan properti navigasi yang berisi entitas terkait Department
.
Untuk menampilkan nama departemen yang ditetapkan untuk kursus:
- Muat entitas terkait
Department
keCourse.Department
dalam properti navigasi. - Dapatkan nama dari
Department
properti entitasName
.
Halaman Kursus Perancah
Ikuti instruksi di halaman Scaffold Student dengan pengecualian berikut:
- Buat folder Halaman/Kursus .
- Gunakan
Course
untuk kelas model. - Gunakan kelas konteks yang ada alih-alih membuat yang baru.
Buka
Pages/Courses/Index.cshtml.cs
dan periksa metode .OnGetAsync
Mesin perancah menentukan pemuatan bersemangat untukDepartment
properti navigasi. MetodeInclude
menentukan pemuatan yang bersemangat.Jalankan aplikasi dan pilih tautan Kursus . Kolom departemen menampilkan
DepartmentID
, yang tidak berguna.
Menampilkan nama departemen
Perbarui Halaman/Kursus/Index.cshtml.cs dengan kode berikut:
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();
}
}
}
Kode sebelumnya mengubah properti menjadi Course
Courses
dan menambahkan AsNoTracking
.
Kueri tanpa pelacakan berguna saat hasilnya digunakan dalam skenario baca-saja. Mereka umumnya lebih cepat untuk dijalankan karena tidak perlu menyiapkan informasi pelacakan perubahan. Jika entitas yang diambil dari database tidak perlu diperbarui, maka kueri tanpa pelacakan kemungkinan akan berkinerja lebih baik daripada kueri pelacakan.
Dalam beberapa kasus, kueri pelacakan lebih efisien daripada kueri tanpa pelacakan. Untuk informasi selengkapnya, lihat Melacak vs. Kueri Tanpa Pelacakan.
Dalam kode sebelumnya, AsNoTracking
dipanggil karena entitas tidak diperbarui dalam konteks saat ini.
Perbarui Pages/Courses/Index.cshtml
dengan kode berikut.
@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>
Perubahan berikut telah dilakukan pada kode perancah:
Course
Mengubah nama properti menjadiCourses
.Menambahkan kolom Angka yang memperlihatkan
CourseID
nilai properti. Secara default, kunci primer tidak di-scaffolding karena biasanya tidak berarti bagi pengguna akhir. Namun, dalam hal ini kunci primer bermakna.Mengubah kolom Departemen untuk menampilkan nama departemen. Kode menampilkan
Name
properti entitas yang dimuatDepartment
keDepartment
dalam properti navigasi:@Html.DisplayFor(modelItem => item.Department.Name)
Jalankan aplikasi dan pilih tab Kursus untuk melihat daftar dengan nama departemen.
Memuat data terkait dengan Pilih
Metode memuat OnGetAsync
data terkait dengan Include
metode . Metode Select
ini adalah alternatif yang hanya memuat data terkait yang diperlukan. Untuk item tunggal, seperti Department.Name
menggunakan SQL INNER JOIN
. Untuk koleksi, ia menggunakan akses database lain, tetapi juga Include
operator pada koleksi.
Kode berikut memuat data terkait dengan Select
metode :
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();
}
Kode sebelumnya tidak mengembalikan jenis entitas apa pun, oleh karena itu tidak ada pelacakan yang dilakukan. Untuk informasi selengkapnya tentang pelacakan EF, lihat Melacak vs. Kueri Tanpa Pelacakan.
CourseViewModel
:
public class CourseViewModel
{
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public string DepartmentName { get; set; }
}
Lihat IndexSelectModel untuk Halaman lengkap Razor .
Membuat halaman Instruktur
Bagian ini membuat perancah halaman Instruktur dan menambahkan Kursus dan Pendaftaran terkait ke halaman Indeks Instruktur.
Halaman ini membaca dan menampilkan data terkait dengan cara berikut:
- Daftar instruktur menampilkan data terkait dari
OfficeAssignment
entitas (Office dalam gambar sebelumnya). EntitasInstructor
danOfficeAssignment
berada dalam hubungan satu-ke-nol-atau-satu. Pemuatan bersemangat digunakan untukOfficeAssignment
entitas. Pemuatan bersemangat biasanya lebih efisien ketika data terkait perlu ditampilkan. Dalam hal ini, tugas kantor untuk instruktur ditampilkan. - Saat pengguna memilih instruktur, entitas terkait
Course
ditampilkan. EntitasInstructor
danCourse
berada dalam hubungan banyak ke banyak. Pemuatan bersemangat digunakan untukCourse
entitas dan entitas terkaitnyaDepartment
. Dalam hal ini, kueri terpisah mungkin lebih efisien karena hanya kursus untuk instruktur yang dipilih yang diperlukan. Contoh ini memperlihatkan cara menggunakan pemuatan bersemangat untuk properti navigasi di entitas yang berada di properti navigasi. - Saat pengguna memilih kursus, data terkait dari
Enrollments
entitas ditampilkan. Pada gambar sebelumnya, nama siswa dan nilai ditampilkan. EntitasCourse
danEnrollment
berada dalam hubungan satu-ke-banyak.
Membuat model tampilan
Halaman instruktur memperlihatkan data dari tiga tabel yang berbeda. Model tampilan diperlukan yang mencakup tiga properti yang mewakili tiga tabel.
Buat Models/SchoolViewModels/InstructorIndexData.cs
dengan kode berikut:
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; }
}
}
Halaman Instruktur Perancah
Ikuti instruksi di Perancah halaman siswa dengan pengecualian berikut:
- Buat folder Halaman/Instruktur .
- Gunakan
Instructor
untuk kelas model. - Gunakan kelas konteks yang ada alih-alih membuat yang baru.
Jalankan aplikasi dan navigasikan ke halaman Instruktur.
Perbarui Pages/Instructors/Index.cshtml.cs
dengan kode berikut:
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;
}
}
}
}
Metode OnGetAsync
ini menerima data rute opsional untuk ID instruktur yang dipilih.
Periksa kueri dalam Pages/Instructors/Index.cshtml.cs
file:
InstructorData = new InstructorIndexData();
InstructorData.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.Courses)
.ThenInclude(c => c.Department)
.OrderBy(i => i.LastName)
.ToListAsync();
Kode menentukan pemuatan bersemangat untuk properti navigasi berikut:
Instructor.OfficeAssignment
Instructor.Courses
Course.Department
Kode berikut dijalankan ketika instruktur dipilih, yaitu, id != null
.
if (id != null)
{
InstructorID = id.Value;
Instructor instructor = InstructorData.Instructors
.Where(i => i.ID == id.Value).Single();
InstructorData.Courses = instructor.Courses;
}
Instruktur yang dipilih diambil dari daftar instruktur dalam model tampilan. Properti model Courses
tampilan dimuat dengan Course
entitas dari properti navigasi instruktur Courses
yang dipilih.
Metode Where
mengembalikan koleksi. Dalam hal ini, filter memilih satu entitas, sehingga metode dipanggil Single
untuk mengonversi koleksi menjadi satu Instructor
entitas. Entitas Instructor
menyediakan akses ke Course
properti navigasi.
Metode Single ini digunakan pada koleksi ketika koleksi hanya memiliki satu item. Metode Single
ini melemparkan pengecualian jika koleksi kosong atau jika ada lebih dari satu item. Alternatifnya adalah SingleOrDefault, yang mengembalikan nilai default jika koleksi kosong. Untuk kueri ini, null
dalam default yang dikembalikan.
Kode berikut mengisi properti model Enrollments
tampilan saat kursus dipilih:
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;
}
Memperbarui halaman Indeks instruktur
Perbarui Pages/Instructors/Index.cshtml
dengan kode berikut.
@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>
}
Kode sebelumnya membuat perubahan berikut:
Memperbarui direktif
page
ke@page "{id:int?}"
."{id:int?}"
adalah templat rute. Templat rute mengubah string kueri bilangan bulat di URL untuk merutekan data. Misalnya, mengklik tautan Pilih untuk instruktur hanya dengan direktif@page
menghasilkan URL seperti berikut:https://localhost:5001/Instructors?id=2
Ketika direktif halaman adalah
@page "{id:int?}"
, URL-nya adalah:https://localhost:5001/Instructors/2
Menambahkan kolom Office yang hanya ditampilkan
item.OfficeAssignment.Location
jikaitem.OfficeAssignment
tidak null. Karena ini adalah hubungan satu-ke-nol-atau-satu, mungkin tidak ada entitas OfficeAssignment terkait.@if (item.OfficeAssignment != null) { @item.OfficeAssignment.Location }
Menambahkan kolom Kursus yang menampilkan kursus yang diajarkan oleh setiap instruktur. Lihat Transisi garis eksplisit untuk informasi selengkapnya tentang sintaks pisau cukur ini.
Menambahkan kode yang secara dinamis ditambahkan
class="table-success"
ketr
elemen instruktur dan kursus yang dipilih. Ini mengatur warna latar belakang untuk baris yang dipilih menggunakan kelas Bootstrap.string selectedRow = ""; if (item.CourseID == Model.CourseID) { selectedRow = "table-success"; } <tr class="@selectedRow">
Menambahkan hyperlink baru berlabel Pilih. Tautan ini mengirimkan ID instruktur yang dipilih ke
Index
metode dan mengatur warna latar belakang.<a asp-action="Index" asp-route-id="@item.ID">Select</a> |
Menambahkan tabel kursus untuk Instruktur yang dipilih.
Menambahkan tabel pendaftaran siswa untuk kursus yang dipilih.
Jalankan aplikasi dan pilih tab Instruktur . Halaman menampilkan Location
(office) dari entitas terkait OfficeAssignment
. Jika OfficeAssignment
null, sel tabel kosong ditampilkan.
Klik tautan Pilih untuk instruktur. Perubahan gaya baris dan kursus yang ditetapkan ke instruktur tersebut ditampilkan.
Pilih kursus untuk melihat daftar siswa terdaftar dan nilai mereka.
Langkah berikutnya
Tutorial berikutnya menunjukkan cara memperbarui data terkait.
Tutorial ini menunjukkan cara membaca dan menampilkan data terkait. Data terkait adalah data yang EF Core dimuat ke dalam properti navigasi.
Ilustrasi berikut menunjukkan halaman yang telah selesai untuk tutorial ini:
Pemuatan bersemangat, eksplisit, dan malas
Ada beberapa cara yang EF Core dapat memuat data terkait ke dalam properti navigasi entitas:
Pemuatan bersemangat. Pemuatan bersemangat adalah ketika kueri untuk satu jenis entitas juga memuat entitas terkait. Saat entitas dibaca, data terkait diambil. Ini biasanya menghasilkan satu kueri gabungan yang mengambil semua data yang diperlukan. EF Core akan mengeluarkan beberapa kueri untuk beberapa jenis pemuatan yang bersemangat. Mengeluarkan beberapa kueri bisa lebih efisien daripada kueri tunggal raksasa. Pemuatan bersemangat ditentukan dengan
Include
metode danThenInclude
.Pemuatan bersemangat mengirim beberapa kueri saat navigasi koleksi disertakan:
- Satu kueri untuk kueri utama
- Satu kueri untuk setiap koleksi "tepi" di pohon beban.
Kueri terpisah dengan
Load
: Data dapat diambil dalam kueri terpisah, dan EF Core "memperbaiki" properti navigasi. "Perbaikan" berarti secara EF Core otomatis mengisi properti navigasi. Kueri terpisah denganLoad
lebih seperti pemuatan eksplisit daripada pemuatan yang bersemangat.Catatan:EF Core secara otomatis memperbaiki properti navigasi ke entitas lain yang sebelumnya dimuat ke dalam instans konteks. Bahkan jika data untuk properti navigasi tidak disertakan secara eksplisit, properti mungkin masih diisi jika beberapa atau semua entitas terkait sebelumnya dimuat.
Pemuatan eksplisit. Saat entitas pertama kali dibaca, data terkait tidak diambil. Kode harus ditulis untuk mengambil data terkait saat diperlukan. Pemuatan eksplisit dengan kueri terpisah menghasilkan beberapa kueri yang dikirim ke database. Dengan pemuatan eksplisit, kode menentukan properti navigasi yang akan dimuat.
Load
Gunakan metode untuk melakukan pemuatan eksplisit. Contohnya:Pemuatan malas. Saat entitas pertama kali dibaca, data terkait tidak diambil. Saat pertama kali properti navigasi diakses, data yang diperlukan untuk properti navigasi tersebut diambil secara otomatis. Kueri dikirim ke database setiap kali properti navigasi diakses untuk pertama kalinya. Pemuatan malas dapat merusak performa, misalnya ketika pengembang menggunakan pola N+1, memuat induk dan menghitung melalui anak-anak.
Membuat halaman Kursus
Entitas Course
menyertakan properti navigasi yang berisi entitas terkait Department
.
Untuk menampilkan nama departemen yang ditetapkan untuk kursus:
- Muat entitas terkait
Department
keCourse.Department
dalam properti navigasi. - Dapatkan nama dari
Department
properti entitasName
.
Halaman Kursus Perancah
Ikuti instruksi di halaman Scaffold Student dengan pengecualian berikut:
- Buat folder Halaman/Kursus .
- Gunakan
Course
untuk kelas model. - Gunakan kelas konteks yang ada alih-alih membuat yang baru.
Buka
Pages/Courses/Index.cshtml.cs
dan periksa metode .OnGetAsync
Mesin perancah menentukan pemuatan bersemangat untukDepartment
properti navigasi. MetodeInclude
menentukan pemuatan yang bersemangat.Jalankan aplikasi dan pilih tautan Kursus . Kolom departemen menampilkan
DepartmentID
, yang tidak berguna.
Menampilkan nama departemen
Perbarui Halaman/Kursus/Index.cshtml.cs dengan kode berikut:
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();
}
}
}
Kode sebelumnya mengubah properti menjadi Course
Courses
dan menambahkan AsNoTracking
. AsNoTracking
meningkatkan performa karena entitas yang dikembalikan tidak dilacak. Entitas tidak perlu dilacak karena tidak diperbarui dalam konteks saat ini.
Perbarui Pages/Courses/Index.cshtml
dengan kode berikut.
@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>
Perubahan berikut telah dilakukan pada kode perancah:
Course
Mengubah nama properti menjadiCourses
.Menambahkan kolom Angka yang memperlihatkan
CourseID
nilai properti. Secara default, kunci primer tidak di-scaffolding karena biasanya tidak berarti bagi pengguna akhir. Namun, dalam hal ini kunci primer bermakna.Mengubah kolom Departemen untuk menampilkan nama departemen. Kode menampilkan
Name
properti entitas yang dimuatDepartment
keDepartment
dalam properti navigasi:@Html.DisplayFor(modelItem => item.Department.Name)
Jalankan aplikasi dan pilih tab Kursus untuk melihat daftar dengan nama departemen.
Memuat data terkait dengan Pilih
Metode memuat OnGetAsync
data terkait dengan Include
metode . Metode Select
ini adalah alternatif yang hanya memuat data terkait yang diperlukan. Untuk item tunggal, seperti Department.Name
menggunakan SQL INNER JOIN. Untuk koleksi, ia menggunakan akses database lain, tetapi juga Include
operator pada koleksi.
Kode berikut memuat data terkait dengan Select
metode :
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();
}
Kode sebelumnya tidak mengembalikan jenis entitas apa pun, oleh karena itu tidak ada pelacakan yang dilakukan. Untuk informasi selengkapnya tentang pelacakan EF, lihat Melacak vs. Kueri Tanpa Pelacakan.
CourseViewModel
:
public class CourseViewModel
{
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public string DepartmentName { get; set; }
}
Lihat IndexSelect.cshtml dan IndexSelect.cshtml.cs untuk contoh lengkapnya.
Membuat halaman Instruktur
Bagian ini membuat perancah halaman Instruktur dan menambahkan Kursus dan Pendaftaran terkait ke halaman Indeks Instruktur.
Halaman ini membaca dan menampilkan data terkait dengan cara berikut:
- Daftar instruktur menampilkan data terkait dari
OfficeAssignment
entitas (Office dalam gambar sebelumnya). EntitasInstructor
danOfficeAssignment
berada dalam hubungan satu-ke-nol-atau-satu. Pemuatan bersemangat digunakan untukOfficeAssignment
entitas. Pemuatan bersemangat biasanya lebih efisien ketika data terkait perlu ditampilkan. Dalam hal ini, tugas kantor untuk instruktur ditampilkan. - Saat pengguna memilih instruktur, entitas terkait
Course
ditampilkan. EntitasInstructor
danCourse
berada dalam hubungan banyak ke banyak. Pemuatan bersemangat digunakan untukCourse
entitas dan entitas terkaitnyaDepartment
. Dalam hal ini, kueri terpisah mungkin lebih efisien karena hanya kursus untuk instruktur yang dipilih yang diperlukan. Contoh ini memperlihatkan cara menggunakan pemuatan bersemangat untuk properti navigasi di entitas yang berada di properti navigasi. - Saat pengguna memilih kursus, data terkait dari
Enrollments
entitas ditampilkan. Pada gambar sebelumnya, nama siswa dan nilai ditampilkan. EntitasCourse
danEnrollment
berada dalam hubungan satu-ke-banyak.
Membuat model tampilan
Halaman instruktur memperlihatkan data dari tiga tabel yang berbeda. Model tampilan diperlukan yang mencakup tiga properti yang mewakili tiga tabel.
Buat SchoolViewModels/InstructorIndexData.cs
dengan kode berikut:
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; }
}
}
Halaman Instruktur Perancah
Ikuti instruksi di Perancah halaman siswa dengan pengecualian berikut:
- Buat folder Halaman/Instruktur .
- Gunakan
Instructor
untuk kelas model. - Gunakan kelas konteks yang ada alih-alih membuat yang baru.
Untuk melihat seperti apa halaman perancah sebelum Anda memperbaruinya, jalankan aplikasi dan navigasikan ke halaman Instruktur.
Perbarui Pages/Instructors/Index.cshtml.cs
dengan kode berikut:
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;
}
}
}
}
Metode OnGetAsync
ini menerima data rute opsional untuk ID instruktur yang dipilih.
Periksa kueri dalam Pages/Instructors/Index.cshtml.cs
file:
InstructorData.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Enrollments)
.ThenInclude(i => i.Student)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
Kode menentukan pemuatan bersemangat untuk properti navigasi berikut:
Instructor.OfficeAssignment
Instructor.CourseAssignments
CourseAssignments.Course
Course.Department
Course.Enrollments
Enrollment.Student
Perhatikan pengulangan Include
metode dan ThenInclude
untuk CourseAssignments
dan Course
. Pengulangan ini diperlukan untuk menentukan pemuatan yang bersemangat untuk dua properti Course
navigasi entitas.
Kode berikut dijalankan saat instruktur dipilih (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);
}
Instruktur yang dipilih diambil dari daftar instruktur dalam model tampilan. Properti model Courses
tampilan dimuat dengan Course
entitas dari properti navigasi instruktur tersebut CourseAssignments
.
Metode Where
mengembalikan koleksi. Tetapi dalam hal ini, filter akan memilih satu entitas, sehingga metode dipanggil Single
untuk mengonversi koleksi menjadi satu Instructor
entitas. Entitas Instructor
menyediakan akses ke CourseAssignments
properti . CourseAssignments
menyediakan akses ke entitas terkait Course
.
Metode Single
ini digunakan pada koleksi ketika koleksi hanya memiliki satu item. Metode Single
ini melemparkan pengecualian jika koleksi kosong atau jika ada lebih dari satu item. Alternatifnya adalah SingleOrDefault
, yang mengembalikan nilai default (null dalam kasus ini) jika koleksi kosong.
Kode berikut mengisi properti model Enrollments
tampilan saat kursus dipilih:
if (courseID != null)
{
CourseID = courseID.Value;
var selectedCourse = InstructorData.Courses
.Where(x => x.CourseID == courseID).Single();
InstructorData.Enrollments = selectedCourse.Enrollments;
}
Memperbarui halaman Indeks instruktur
Perbarui Pages/Instructors/Index.cshtml
dengan kode berikut.
@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>
}
Kode sebelumnya membuat perubahan berikut:
Memperbarui direktif
page
dari@page
ke@page "{id:int?}"
."{id:int?}"
adalah templat rute. Templat rute mengubah string kueri bilangan bulat di URL untuk merutekan data. Misalnya, mengklik tautan Pilih untuk instruktur hanya dengan direktif@page
menghasilkan URL seperti berikut:https://localhost:5001/Instructors?id=2
Ketika direktif halaman adalah
@page "{id:int?}"
, URL-nya adalah:https://localhost:5001/Instructors/2
Menambahkan kolom Office yang hanya ditampilkan
item.OfficeAssignment.Location
jikaitem.OfficeAssignment
tidak null. Karena ini adalah hubungan satu-ke-nol-atau-satu, mungkin tidak ada entitas OfficeAssignment terkait.@if (item.OfficeAssignment != null) { @item.OfficeAssignment.Location }
Menambahkan kolom Kursus yang menampilkan kursus yang diajarkan oleh setiap instruktur. Lihat Transisi garis eksplisit untuk informasi selengkapnya tentang sintaks pisau cukur ini.
Menambahkan kode yang secara dinamis ditambahkan
class="table-success"
ketr
elemen instruktur dan kursus yang dipilih. Ini mengatur warna latar belakang untuk baris yang dipilih menggunakan kelas Bootstrap.string selectedRow = ""; if (item.CourseID == Model.CourseID) { selectedRow = "table-success"; } <tr class="@selectedRow">
Menambahkan hyperlink baru berlabel Pilih. Tautan ini mengirimkan ID instruktur yang dipilih ke
Index
metode dan mengatur warna latar belakang.<a asp-action="Index" asp-route-id="@item.ID">Select</a> |
Menambahkan tabel kursus untuk Instruktur yang dipilih.
Menambahkan tabel pendaftaran siswa untuk kursus yang dipilih.
Jalankan aplikasi dan pilih tab Instruktur . Halaman menampilkan Location
(office) dari entitas terkait OfficeAssignment
. Jika OfficeAssignment
null, sel tabel kosong ditampilkan.
Klik tautan Pilih untuk instruktur. Perubahan gaya baris dan kursus yang ditetapkan ke instruktur tersebut ditampilkan.
Pilih kursus untuk melihat daftar siswa terdaftar dan nilai mereka.
Menggunakan Tunggal
Metode Single
ini dapat meneruskan Where
kondisi alih-alih memanggil Where
metode secara terpisah:
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;
}
}
Penggunaan Single
dengan kondisi Di mana adalah masalah preferensi pribadi. Ini tidak memberikan manfaat daripada menggunakan Where
metode .
Pemuatan eksplisit
Kode saat ini menentukan pemuatan yang bersemangat untuk Enrollments
dan 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();
Misalkan pengguna jarang ingin melihat pendaftaran dalam kursus. Dalam hal ini, pengoptimalan hanya akan memuat data pendaftaran jika diminta. Di bagian ini, diperbarui OnGetAsync
untuk menggunakan pemuatan eksplisit dan Enrollments
Students
.
Perbarui Pages/Instructors/Index.cshtml.cs
dengan kode berikut.
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;
}
}
}
}
Kode sebelumnya menghilangkan panggilan metode ThenInclude untuk pendaftaran dan data siswa. Jika kursus dipilih, kode pemuatan eksplisit mengambil:
- Entitas
Enrollment
untuk kursus yang dipilih. - Entitas
Student
untuk masing-masingEnrollment
.
Perhatikan bahwa kode sebelumnya berkomentar .AsNoTracking()
. Properti navigasi hanya dapat dimuat secara eksplisit untuk entitas terlacak.
Menguji aplikasi. Dari perspektif pengguna, aplikasi berperilaku identik dengan versi sebelumnya.
Langkah berikutnya
Tutorial berikutnya menunjukkan cara memperbarui data terkait.
Dalam tutorial ini, data terkait dibaca dan ditampilkan. Data terkait adalah data yang EF Core dimuat ke dalam properti navigasi.
Jika Mengalami masalah, Anda tidak dapat menyelesaikan, mengunduh, atau melihat aplikasi yang telah selesai. Unduh instruksi.
Ilustrasi berikut menunjukkan halaman yang telah selesai untuk tutorial ini:
Bersemangat, eksplisit, dan malas Memuat data terkait
Ada beberapa cara yang EF Core dapat memuat data terkait ke dalam properti navigasi entitas:
Pemuatan bersemangat. Pemuatan bersemangat adalah ketika kueri untuk satu jenis entitas juga memuat entitas terkait. Saat entitas dibaca, data terkait diambil. Ini biasanya menghasilkan satu kueri gabungan yang mengambil semua data yang diperlukan. EF Core akan mengeluarkan beberapa kueri untuk beberapa jenis pemuatan yang bersemangat. Menerbitkan beberapa kueri bisa lebih efisien daripada yang terjadi untuk beberapa kueri di EF6 di mana ada satu kueri. Pemuatan bersemangat ditentukan dengan
Include
metode danThenInclude
.Pemuatan bersemangat mengirim beberapa kueri saat navigasi koleksi disertakan:
- Satu kueri untuk kueri utama
- Satu kueri untuk setiap koleksi "tepi" di pohon beban.
Kueri terpisah dengan
Load
: Data dapat diambil dalam kueri terpisah, dan EF Core "memperbaiki" properti navigasi. "memperbaiki" berarti secara EF Core otomatis mengisi properti navigasi. Kueri terpisah denganLoad
lebih seperti pemuatan eksplisit daripada pemuatan yang bersemangat.Catatan: EF Core secara otomatis memperbaiki properti navigasi ke entitas lain yang sebelumnya dimuat ke dalam instans konteks. Bahkan jika data untuk properti navigasi tidak disertakan secara eksplisit, properti mungkin masih diisi jika beberapa atau semua entitas terkait sebelumnya dimuat.
Pemuatan eksplisit. Saat entitas pertama kali dibaca, data terkait tidak diambil. Kode harus ditulis untuk mengambil data terkait saat diperlukan. Pemuatan eksplisit dengan kueri terpisah menghasilkan beberapa kueri yang dikirim ke DB. Dengan pemuatan eksplisit, kode menentukan properti navigasi yang akan dimuat.
Load
Gunakan metode untuk melakukan pemuatan eksplisit. Contohnya:Pemuatan malas. Pemuatan malas ditambahkan ke EF Core dalam versi 2.1. Saat entitas pertama kali dibaca, data terkait tidak diambil. Saat pertama kali properti navigasi diakses, data yang diperlukan untuk properti navigasi tersebut diambil secara otomatis. Kueri dikirim ke DB setiap kali properti navigasi diakses untuk pertama kalinya.
Operator
Select
hanya memuat data terkait yang diperlukan.
Membuat halaman Kursus yang menampilkan nama departemen
Entitas Kursus menyertakan properti navigasi yang berisi Department
entitas. Entitas Department
berisi departemen tempat kursus ditetapkan.
Untuk menampilkan nama departemen yang ditetapkan dalam daftar kursus:
Name
Dapatkan properti dariDepartment
entitas.- Entitas
Department
berasal dariCourse.Department
properti navigasi.
Perancah model Kursus
Ikuti instruksi di Perancah model siswa dan gunakan Course
untuk kelas model.
Perintah sebelumnya mengacak Course
model. Buka proyek di Visual Studio.
Buka Pages/Courses/Index.cshtml.cs
dan periksa metode .OnGetAsync
Mesin perancah menentukan pemuatan bersemangat untuk Department
properti navigasi. Metode Include
menentukan pemuatan yang bersemangat.
Jalankan aplikasi dan pilih tautan Kursus . Kolom departemen menampilkan DepartmentID
, yang tidak berguna.
OnGetAsync
Perbarui metode dengan kode berikut:
public async Task OnGetAsync()
{
Course = await _context.Courses
.Include(c => c.Department)
.AsNoTracking()
.ToListAsync();
}
Kode sebelumnya menambahkan AsNoTracking
. AsNoTracking
meningkatkan performa karena entitas yang dikembalikan tidak dilacak. Entitas tidak dilacak karena tidak diperbarui dalam konteks saat ini.
Perbarui Pages/Courses/Index.cshtml
dengan markup yang disorot berikut ini:
@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>
Perubahan berikut telah dilakukan pada kode perancah:
Mengubah judul dari Indeks ke Kursus.
Menambahkan kolom Angka yang memperlihatkan
CourseID
nilai properti. Secara default, kunci primer tidak di-scaffolding karena biasanya tidak berarti bagi pengguna akhir. Namun, dalam hal ini kunci primer bermakna.Mengubah kolom Departemen untuk menampilkan nama departemen. Kode menampilkan
Name
properti entitas yang dimuatDepartment
keDepartment
dalam properti navigasi:@Html.DisplayFor(modelItem => item.Department.Name)
Jalankan aplikasi dan pilih tab Kursus untuk melihat daftar dengan nama departemen.
Memuat data terkait dengan Pilih
Metode memuat OnGetAsync
data terkait dengan Include
metode :
public async Task OnGetAsync()
{
Course = await _context.Courses
.Include(c => c.Department)
.AsNoTracking()
.ToListAsync();
}
Operator Select
hanya memuat data terkait yang diperlukan. Untuk item tunggal, seperti Department.Name
menggunakan SQL INNER JOIN. Untuk koleksi, ia menggunakan akses database lain, tetapi juga Include
operator pada koleksi.
Kode berikut memuat data terkait dengan Select
metode :
public IList<CourseViewModel> CourseVM { get; set; }
public async Task OnGetAsync()
{
CourseVM = await _context.Courses
.Select(p => new CourseViewModel
{
CourseID = p.CourseID,
Title = p.Title,
Credits = p.Credits,
DepartmentName = p.Department.Name
}).ToListAsync();
}
CourseViewModel
:
public class CourseViewModel
{
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public string DepartmentName { get; set; }
}
Lihat IndexSelect.cshtml dan IndexSelect.cshtml.cs untuk contoh lengkapnya.
Membuat halaman Instruktur yang memperlihatkan Kursus dan Pendaftaran
Di bagian ini, halaman Instruktur dibuat.
Halaman ini membaca dan menampilkan data terkait dengan cara berikut:
- Daftar instruktur menampilkan data terkait dari
OfficeAssignment
entitas (Office dalam gambar sebelumnya). EntitasInstructor
danOfficeAssignment
berada dalam hubungan satu-ke-nol-atau-satu. Pemuatan bersemangat digunakan untukOfficeAssignment
entitas. Pemuatan bersemangat biasanya lebih efisien ketika data terkait perlu ditampilkan. Dalam hal ini, tugas kantor untuk instruktur ditampilkan. - Saat pengguna memilih instruktur (Harui dalam gambar sebelumnya), entitas terkait
Course
ditampilkan. EntitasInstructor
danCourse
berada dalam hubungan banyak ke banyak. Pemuatan bersemangat digunakan untukCourse
entitas dan entitas terkaitnyaDepartment
. Dalam hal ini, kueri terpisah mungkin lebih efisien karena hanya kursus untuk instruktur yang dipilih yang diperlukan. Contoh ini memperlihatkan cara menggunakan pemuatan bersemangat untuk properti navigasi di entitas yang berada di properti navigasi. - Saat pengguna memilih kursus (Kimia dalam gambar sebelumnya), data terkait dari
Enrollments
entitas ditampilkan. Pada gambar sebelumnya, nama siswa dan nilai ditampilkan. EntitasCourse
danEnrollment
berada dalam hubungan satu-ke-banyak.
Membuat model tampilan untuk tampilan Indeks Instruktur
Halaman instruktur memperlihatkan data dari tiga tabel yang berbeda. Model tampilan dibuat yang menyertakan tiga entitas yang mewakili tiga tabel.
Di folder SchoolViewModels, buat InstructorIndexData.cs
dengan kode berikut:
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; }
}
}
Perancah model Instruktur
Ikuti instruksi di Perancah model siswa dan gunakan Instructor
untuk kelas model.
Perintah sebelumnya mengacak Instructor
model.
Jalankan aplikasi dan navigasikan ke halaman instruktur.
Ganti Pages/Instructors/Index.cshtml.cs
dengan kode berikut:
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;
}
}
}
}
Metode OnGetAsync
ini menerima data rute opsional untuk ID instruktur yang dipilih.
Periksa kueri dalam Pages/Instructors/Index.cshtml.cs
file:
Instructor.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
Kueri memiliki dua meliputi:
OfficeAssignment
: Ditampilkan dalam tampilan instruktur.CourseAssignments
: Yang membawa kursus diajarkan.
Memperbarui halaman Indeks instruktur
Perbarui Pages/Instructors/Index.cshtml
dengan markup berikut:
@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>
Markup sebelumnya membuat perubahan berikut:
Memperbarui direktif
page
dari@page
ke@page "{id:int?}"
."{id:int?}"
adalah templat rute. Templat rute mengubah string kueri bilangan bulat di URL untuk merutekan data. Misalnya, mengklik tautan Pilih untuk instruktur hanya dengan direktif@page
menghasilkan URL seperti berikut:http://localhost:1234/Instructors?id=2
Ketika direktif halaman adalah
@page "{id:int?}"
, URL sebelumnya adalah:http://localhost:1234/Instructors/2
Judul halaman adalah Instruktur.
Menambahkan kolom Office yang hanya ditampilkan
item.OfficeAssignment.Location
jikaitem.OfficeAssignment
tidak null. Karena ini adalah hubungan satu-ke-nol-atau-satu, mungkin tidak ada entitas OfficeAssignment terkait.@if (item.OfficeAssignment != null) { @item.OfficeAssignment.Location }
Menambahkan kolom Kursus yang menampilkan kursus yang diajarkan oleh setiap instruktur. Lihat Transisi garis eksplisit untuk informasi selengkapnya tentang sintaks pisau cukur ini.
Menambahkan kode yang secara dinamis ditambahkan
class="success"
ketr
elemen instruktur yang dipilih. Ini mengatur warna latar belakang untuk baris yang dipilih menggunakan kelas Bootstrap.string selectedRow = ""; if (item.CourseID == Model.CourseID) { selectedRow = "success"; } <tr class="@selectedRow">
Menambahkan hyperlink baru berlabel Pilih. Tautan ini mengirimkan ID instruktur yang dipilih ke
Index
metode dan mengatur warna latar belakang.<a asp-action="Index" asp-route-id="@item.ID">Select</a> |
Jalankan aplikasi dan pilih tab Instruktur . Halaman menampilkan Location
(office) dari entitas terkait OfficeAssignment
. Jika OfficeAssignment' null, sel tabel kosong akan ditampilkan.
Klik tautan Pilih . Gaya baris berubah.
Menambahkan kursus yang diajarkan oleh instruktur yang dipilih
OnGetAsync
Perbarui metode dengan Pages/Instructors/Index.cshtml.cs
kode berikut:
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;
}
}
Menambahkan 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;
}
}
Periksa kueri yang diperbarui:
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();
Kueri sebelumnya menambahkan Department
entitas.
Kode berikut dijalankan saat instruktur dipilih (id != null
). Instruktur yang dipilih diambil dari daftar instruktur dalam model tampilan. Properti model Courses
tampilan dimuat dengan Course
entitas dari properti navigasi instruktur tersebut 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);
}
Metode Where
mengembalikan koleksi. Dalam metode sebelumnya Where
, hanya satu Instructor
entitas yang dikembalikan. Metode ini Single
mengonversi koleksi menjadi satu Instructor
entitas. Entitas Instructor
menyediakan akses ke CourseAssignments
properti . CourseAssignments
menyediakan akses ke entitas terkait Course
.
Metode Single
ini digunakan pada koleksi ketika koleksi hanya memiliki satu item. Metode Single
ini melemparkan pengecualian jika koleksi kosong atau jika ada lebih dari satu item. Alternatifnya adalah SingleOrDefault
, yang mengembalikan nilai default (null dalam kasus ini) jika koleksi kosong. Menggunakan SingleOrDefault
pada koleksi kosong:
- Menghasilkan pengecualian (dari mencoba menemukan
Courses
properti pada referensi null). - Pesan pengecualian akan kurang jelas menunjukkan penyebab masalah.
Kode berikut mengisi properti model Enrollments
tampilan saat kursus dipilih:
if (courseID != null)
{
CourseID = courseID.Value;
Instructor.Enrollments = Instructor.Courses.Where(
x => x.CourseID == courseID).Single().Enrollments;
}
Tambahkan markup berikut ke akhir Pages/Instructors/Index.cshtml
Razor Halaman:
<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>
}
Markup sebelumnya menampilkan daftar kursus yang terkait dengan instruktur saat instruktur dipilih.
Menguji aplikasi. Klik tautan Pilih di halaman instruktur.
Perlihatkan data siswa
Di bagian ini, aplikasi diperbarui untuk menampilkan data siswa untuk kursus yang dipilih.
Perbarui kueri dalam OnGetAsync
metode dengan Pages/Instructors/Index.cshtml.cs
kode berikut:
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();
Perbarui Pages/Instructors/Index.cshtml
. Tambahkan markup berikut ke akhir file:
@if (Model.Instructor.Enrollments != null)
{
<h3>
Students Enrolled in Selected Course
</h3>
<table class="table">
<tr>
<th>Name</th>
<th>Grade</th>
</tr>
@foreach (var item in Model.Instructor.Enrollments)
{
<tr>
<td>
@item.Student.FullName
</td>
<td>
@Html.DisplayFor(modelItem => item.Grade)
</td>
</tr>
}
</table>
}
Markup sebelumnya menampilkan daftar siswa yang terdaftar dalam kursus yang dipilih.
Refresh halaman dan pilih instruktur. Pilih kursus untuk melihat daftar siswa terdaftar dan nilai mereka.
Menggunakan Tunggal
Metode Single
ini dapat meneruskan Where
kondisi alih-alih memanggil Where
metode secara terpisah:
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;
}
}
Pendekatan sebelumnya Single
tidak memberikan manfaat daripada menggunakan Where
. Beberapa pengembang lebih suka Single
gaya pendekatan.
Pemuatan eksplisit
Kode saat ini menentukan pemuatan yang bersemangat untuk Enrollments
dan 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();
Misalkan pengguna jarang ingin melihat pendaftaran dalam kursus. Dalam hal ini, pengoptimalan hanya akan memuat data pendaftaran jika diminta. Di bagian ini, diperbarui OnGetAsync
untuk menggunakan pemuatan eksplisit dan Enrollments
Students
.
OnGetAsync
Perbarui dengan kode berikut:
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;
}
}
Kode sebelumnya menghilangkan panggilan metode ThenInclude untuk pendaftaran dan data siswa. Jika kursus dipilih, kode yang disorot akan mengambil:
- Entitas
Enrollment
untuk kursus yang dipilih. - Entitas
Student
untuk masing-masingEnrollment
.
Perhatikan komentar .AsNoTracking()
kode sebelumnya . Properti navigasi hanya dapat dimuat secara eksplisit untuk entitas terlacak.
Menguji aplikasi. Dari perspektif pengguna, aplikasi berperilaku identik dengan versi sebelumnya.
Tutorial berikutnya menunjukkan cara memperbarui data terkait.
Sumber Daya Tambahan:
ASP.NET Core