Nguon: Microsoft Learn · .NET 8.0

Phần 6: Razor Pages với EF Core trong ASP.NET Core - Đọc Dữ Liệu Liên Quan

Nguồn: Part 6: Razor Pages - EF Core Read Related Data

Hướng dẫn này minh họa cách đọc và hiển thị dữ liệu liên quan. Dữ liệu liên quan là dữ liệu mà EF Core tải vào navigation properties (thuộc tính điều hướng) của entity.

Eager, Explicit, và Lazy Loading

Có một số cách EF Core có thể tải dữ liệu liên quan vào navigation properties của entity:

Eager loading gửi nhiều truy vấn khi navigation collection được bao gồm:

Lưu ý: EF Core tự động sửa lại navigation properties cho bất kỳ entity nào đã được tải trước đó vào context instance. Ngay cả khi dữ liệu cho navigation property không được bao gồm rõ ràng, thuộc tính vẫn có thể được điền nếu một số hoặc tất cả các entity liên quan đã được tải trước đó.

Tạo các trang Course

Entity Course bao gồm navigation property chứa entity Department liên quan.

Để hiển thị tên department (khoa) được phân công cho course:

Scaffold các trang Course

Visual Studio

Visual Studio Code

Trên Windows:

``dotnetcli dotnet aspnet-codegenerator razorpage -m Course -dc SchoolContext -udl -outDir Pages\Courses --referenceScriptLibraries ``

Trên Linux hoặc macOS:

``dotnetcli dotnet aspnet-codegenerator razorpage -m Course -dc SchoolContext -udl -outDir Pages/Courses --referenceScriptLibraries ``


Hiển thị tên department

Cập nhật Pages/Courses/Index.cshtml.cs với code sau:

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

Code trên thay đổi thuộc tính Course thành Courses và thêm AsNoTracking.

No-tracking queries (truy vấn không theo dõi) hữu ích khi kết quả được sử dụng trong tình huống chỉ đọc. Chúng thường thực thi nhanh hơn vì không cần thiết lập thông tin theo dõi thay đổi.

Cập nhật Pages/Courses/Index.cshtml với code sau:

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

Những thay đổi được thực hiện:

Chạy ứng dụng và chọn tab Courses để xem danh sách với tên department.

Tải dữ liệu liên quan với Select

Phương thức OnGetAsync tải dữ liệu liên quan bằng phương thức Include. Phương thức Select là phương án thay thế chỉ tải dữ liệu liên quan cần thiết. Với các item đơn lẻ như Department.Name, nó sử dụng SQL INNER JOIN. Với collections, nó sử dụng truy cập database khác, tương tự như toán tử Include trên collections.

Code sau tải dữ liệu liên quan bằng phương thức Select:

csharp
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:

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

Tạo các trang Instructor

Phần này scaffold các trang Instructor và thêm các Courses và Enrollments liên quan vào trang Instructors Index.

Trang này đọc và hiển thị dữ liệu liên quan theo các cách sau:

Tạo view model

Tạo Models/SchoolViewModels/InstructorIndexData.cs với code sau:

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

Scaffold các trang Instructor

Visual Studio

Visual Studio Code

Trên Windows:

``dotnetcli dotnet aspnet-codegenerator razorpage -m Instructor -dc SchoolContext -udl -outDir Pages\Instructors --referenceScriptLibraries ``

Trên Linux hoặc macOS:

``dotnetcli dotnet aspnet-codegenerator razorpage -m Instructor -dc SchoolContext -udl -outDir Pages/Instructors --referenceScriptLibraries ``


Cập nhật Pages/Instructors/Index.cshtml.cs với code sau:

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

Phương thức OnGetAsync nhận route data tùy chọn cho ID của instructor được chọn.

Code chỉ định eager loading cho các navigation properties sau:

Khi một instructor được chọn (id != null), instructor được chọn được lấy từ danh sách instructors trong view model. Thuộc tính Courses của view model được tải với các entity Course từ navigation property Courses của instructor được chọn.

Phương thức Where trả về collection. Trong trường hợp này, filter chọn một entity duy nhất, vì vậy phương thức Single được gọi để chuyển đổi collection thành entity Instructor duy nhất.

Cập nhật trang Instructors Index

Cập nhật Pages/Instructors/Index.cshtml với code sau:

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

Code trên thực hiện các thay đổi sau:

Chạy ứng dụng và chọn tab Instructors. Trang hiển thị Location (văn phòng) từ entity OfficeAssignment liên quan. Nếu OfficeAssignment là null, ô bảng trống được hiển thị.

Nhấp vào link Select cho một instructor. Kiểu hàng thay đổi và các courses được phân công cho instructor đó được hiển thị.

Chọn một course để xem danh sách sinh viên đã đăng ký và điểm của họ.