Bagikan melalui


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:

Halaman Indeks Kursus

Halaman Indeks Instruktur

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 .

    Contoh pemuatan bersemangat

    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 dengan Load lebih seperti pemuatan eksplisit daripada pemuatan yang bersemangat.

    Contoh kueri terpisah

    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:

    Contoh pemuatan eksplisit

  • 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 .

Course.Department

Untuk menampilkan nama departemen yang ditetapkan untuk kursus:

  • Muat entitas terkait Department ke Course.Department dalam properti navigasi.
  • Dapatkan nama dari Department properti entitas Name .

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 untuk Department properti navigasi. Metode Include 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 menjadi Courses.

  • 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 dimuat Department ke Department dalam properti navigasi:

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

Jalankan aplikasi dan pilih tab Kursus untuk melihat daftar dengan nama departemen.

Halaman Indeks Kursus

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 Indeks Instruktur

Halaman ini membaca dan menampilkan data terkait dengan cara berikut:

  • Daftar instruktur menampilkan data terkait dari OfficeAssignment entitas (Office dalam gambar sebelumnya). Entitas Instructor dan OfficeAssignment berada dalam hubungan satu-ke-nol-atau-satu. Pemuatan bersemangat digunakan untuk OfficeAssignment 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. Entitas Instructor dan Course berada dalam hubungan banyak ke banyak. Pemuatan bersemangat digunakan untuk Course entitas dan entitas terkaitnya Department . 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. Entitas Course dan Enrollment 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 jika item.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" ke tr 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.

Instruktur dan kursus halaman Indeks Instruktur dipilih

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:

Halaman Indeks Kursus

Halaman Indeks Instruktur

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 dan ThenInclude .

    Contoh pemuatan bersemangat

    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 dengan Load lebih seperti pemuatan eksplisit daripada pemuatan yang bersemangat.

    Contoh kueri terpisah

    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:

    Contoh pemuatan eksplisit

  • 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 .

Course.Department

Untuk menampilkan nama departemen yang ditetapkan untuk kursus:

  • Muat entitas terkait Department ke Course.Department dalam properti navigasi.
  • Dapatkan nama dari Department properti entitas Name .

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 untuk Department properti navigasi. Metode Include 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 menjadi Courses.

  • 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 dimuat Department ke Department dalam properti navigasi:

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

Jalankan aplikasi dan pilih tab Kursus untuk melihat daftar dengan nama departemen.

Halaman Indeks Kursus

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 Indeks Instruktur

Halaman ini membaca dan menampilkan data terkait dengan cara berikut:

  • Daftar instruktur menampilkan data terkait dari OfficeAssignment entitas (Office dalam gambar sebelumnya). Entitas Instructor dan OfficeAssignment berada dalam hubungan satu-ke-nol-atau-satu. Pemuatan bersemangat digunakan untuk OfficeAssignment 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. Entitas Instructor dan Course berada dalam hubungan banyak ke banyak. Pemuatan bersemangat digunakan untuk Course entitas dan entitas terkaitnya Department . 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. Entitas Course dan Enrollment 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 .

Instruktur-ke-Kursus m:M

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 jika item.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" ke tr 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.

Instruktur dan kursus halaman Indeks Instruktur dipilih

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-masing Enrollment.

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:

Halaman Indeks Kursus

Halaman Indeks Instruktur

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 dan ThenInclude .

    Contoh pemuatan bersemangat

    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 dengan Load lebih seperti pemuatan eksplisit daripada pemuatan yang bersemangat.

    Contoh kueri terpisah

    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:

    Contoh pemuatan eksplisit

  • 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 dari Department entitas.
  • Entitas Department berasal dari Course.Department properti navigasi.

Course.Department

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 dimuat Department ke Department dalam properti navigasi:

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

Jalankan aplikasi dan pilih tab Kursus untuk melihat daftar dengan nama departemen.

Halaman Indeks Kursus

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 Indeks Instruktur

Halaman ini membaca dan menampilkan data terkait dengan cara berikut:

  • Daftar instruktur menampilkan data terkait dari OfficeAssignment entitas (Office dalam gambar sebelumnya). Entitas Instructor dan OfficeAssignment berada dalam hubungan satu-ke-nol-atau-satu. Pemuatan bersemangat digunakan untuk OfficeAssignment 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. Entitas Instructor dan Course berada dalam hubungan banyak ke banyak. Pemuatan bersemangat digunakan untuk Course entitas dan entitas terkaitnya Department . 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. Entitas Course dan Enrollment 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 jika item.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" ke tr 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 .

Instruktur-ke-Kursus m:M

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.cshtmlRazor 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.

Instruktur dan kursus halaman Indeks Instruktur dipilih

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-masing Enrollment.

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: