第 6 部分:ASP.NET Core 中具有 EF Core 的 Razor Pages - 讀取相關資料
作者:Tom Dykstra、Jon P Smith 和 Rick Anderson
Contoso 大學 Web 應用程式將示範如何使用 EF Core 和 Visual Studio 來建立 Razor Pages Web 應用程式。 如需教學課程系列的資訊,請參閱第一個教學課程。
如果您遇到無法解決的問題,請下載已完成的應用程式,並遵循本教學課程以將程式碼與您所建立的內容進行比較。
本教學課程說明如何讀取及顯示相關資料。 相關資料是 EF Core 載入到導覽屬性的資料。
下圖顯示本教學課程的已完成頁面:
積極式、明確式和消極式載入
EF Core 有幾種方式可以將相關資料載入到實體的導覽屬性:
積極式載入。 積極式載入是指某一類型實體的查詢同時也會載入相關實體。 讀取實體時,將會擷取其相關資料。 這通常會導致單一聯結查詢,其可擷取所有需要的資料。 EF Core 將針對某些類型的積極式載入發出多個查詢。 發出多個查詢可能比大型單一查詢更有效率。 積極式載入是使用 Include 和 ThenInclude 方法加以指定。
當集合導覽包含在內時,積極式載入會傳送多個查詢:
- 針對主查詢傳送一個查詢
- 針對負載樹狀結構中的每個集合「邊緣」傳送一個查詢。
使用
Load
的個別查詢:資料可以在個別查詢中擷取,而 EF Core 會「修正」導覽屬性。 「修正」表示 EF Core 會自動填入導覽屬性。 使用Load
的個別查詢更像是明確式載入,而不是積極式載入。注意:EF Core 會將導覽屬性自動修正為先前已載入至內容執行個體的任何其他實體。 即使「未」明確包含導覽屬性的資料,如果先前已載入某些或所有相關實體,仍然可能會填入該屬性。
明確式載入。 第一次讀取實體時,不會擷取相關資料。 必須撰寫程式碼,才能在需要時擷取相關資料。 使用個別查詢的明確式載入會導致多個查詢傳送至資料庫。 透過明確式載入,程式碼會指定要載入的導覽屬性。 請使用
Load
方法來執行明確式載入。 例如:消極式載入。 第一次讀取實體時,不會擷取相關資料。 第一次存取導覽屬性時,將會自動擷取該導覽屬性所需的資料。 每當第一次存取導覽屬性時,查詢會傳送至資料庫。 延遲載入可能會損害效能,例如當開發人員使用 N+1 查詢時。 N+1 查詢會載入父系並列舉子系。
建立 Course 頁面
Course
實體包含導覽屬性,其中包含相關的 Department
實體。
若要顯示針對課程指派的部門名稱:
- 將相關的
Department
實體載入Course.Department
導覽屬性。 - 從
Department
實體的Name
屬性取得名稱。
Scaffold Course 頁面
遵循 Scaffold Student 頁面中的指示,下列部分除外:
- 建立 Pages/Courses 資料夾。
- 使用
Course
作為模型類別。 - 使用現有內容類別,而非建立新的類別。
開啟
Pages/Courses/Index.cshtml.cs
並檢查OnGetAsync
方法。 Scaffolding 引擎已針對Department
導覽屬性指定積極式載入。Include
方法可指定積極式載入。執行應用程式並選取課程連結。 部門資料行便會顯示沒有用的
DepartmentID
。
顯示部門名稱
使用下列程式碼更新 Pages/Courses/Index.cshtml.cs:
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();
}
}
}
上述程式碼會將 Course
屬性變更為 Courses
,並新增 AsNoTracking
。
如果要在唯讀案例中使用結果,則不追蹤的查詢很實用。 其執行速度通常較快,因為不需要設定變更追蹤資訊。 如果從資料庫擷取的實體不需要更新,則不追蹤查詢的執行效能可能會優於追蹤查詢。
在某些情況下,追蹤查詢比不追蹤查詢更有效率。 如需詳細資訊,請參閱追蹤與不追蹤的查詢。
在上述程式碼中會呼叫 AsNoTracking
,因為實體不會在目前內容中更新。
以下列程式碼來更新 Pages/Courses/Index.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>
已對包含 Scaffold 的程式碼進行下列變更:
已將
Course
屬性名稱變更為Courses
。新增顯示
CourseID
屬性值的 [編號] 資料行。 主索引鍵預設不會進行 Scaffold,因為它們對終端使用者通常沒有任何意義。 不過,在此情況下,主索引鍵有意義。變更 [部門] 資料行來顯示部門名稱。 此程式碼會顯示已載入到
Department
導覽屬性之Department
實體的Name
屬性:@Html.DisplayFor(modelItem => item.Department.Name)
執行應用程式,並選取 [Courses] 索引標籤來查看含有部門名稱的清單。
使用 Select 載入相關資料
OnGetAsync
方法使用 Include
方法載入相關資料。 Select
方法是一種替代方案,只會載入所需的相關資料。 如果是單一項目 (例如 Department.Name
),它會使用 SQL INNER JOIN
。 如果是集合,它會使用另一種資料庫存取,但集合上的 Include
運算子也是如此。
下列程式碼使用 Select
方法載入相關資料:
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();
}
上述程式碼不會傳回任何實體類型,因此不會進行任何追蹤。 如需 EF 追蹤的詳細資訊,請參閱追蹤與不追蹤的查詢。
CourseViewModel
:
public class CourseViewModel
{
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public string DepartmentName { get; set; }
}
如需完整的 Razor 頁面,請參閱IndexSelectModel。
建立 Instructor 頁面
本節會 scaffold Instructor 頁面,並將相關的課程和註冊新增至 Instructors 索引頁面。
此頁面將以下列方式讀取和顯示相關資料:
- 講師清單會顯示來自
OfficeAssignment
實體 (上述映像中的 Office) 的相關資料。Instructor
與OfficeAssignment
實體具有一對零或一關聯性。 積極式載入用於OfficeAssignment
實體。 若需要顯示相關資料,積極式載入通常更有效率。 在此情況下,將會顯示講師的辦公室指派。 - 當使用者選取講師時,將會顯示相關的
Course
實體。Instructor
與Course
實體具有多對多關聯性。 將會針對Course
實體和其相關Department
實體使用積極式載入。 在此情況下,個別查詢可能更有效率,因為只需要所選取講師的課程。 這個範例示範如何使用導覽屬性中實體之導覽屬性的積極式載入。 - 當使用者選取課程時,將會顯示來自
Enrollments
實體的相關資料。 在上述映像中,將會顯示學生姓名和年級。Course
與Enrollment
實體具有一對多關聯性。
建立檢視模型
講師頁面會顯示下列三個不同資料表的資料。 需要檢視模型,其包含代表三個資料表的三個屬性。
使用下列程式碼建立 Models/SchoolViewModels/InstructorIndexData.cs
:
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 Instructor 頁面
遵循 Scaffold Students 頁面中的指示,下列部分除外:
- 建立 Pages/Instructors 資料夾。
- 使用
Instructor
作為模型類別。 - 使用現有內容類別,而非建立新的類別。
執行應用程式並巡覽至講師頁面。
以下列程式碼來更新 Pages/Instructors/Index.cshtml.cs
:
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;
}
}
}
}
OnGetAsync
方法會針對所選取講師的識別碼接受選擇性的路由資料。
檢查 Pages/Instructors/Index.cshtml.cs
檔案中的查詢:
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();
這個程式碼會針對下列導覽屬性指定積極式載入:
Instructor.OfficeAssignment
Instructor.Courses
Course.Department
下列程式碼會在已選取講師時執行,也就是 id != null
。
if (id != null)
{
InstructorID = id.Value;
Instructor instructor = InstructorData.Instructors
.Where(i => i.ID == id.Value).Single();
InstructorData.Courses = instructor.Courses;
}
選取的講師會從檢視模型的講師清單中擷取。 檢視模型的 Courses
屬性會使用 Course
實體從所選講師的 Courses
導覽屬性載入。
Where
方法會傳回集合。 在此情況下,篩選條件會選取單一實體,因此會呼叫 Single
方法,將集合轉換成單一 Instructor
實體。 Instructor
實體提供對 Course
導覽屬性的存取。
當集合只有一個項目時,將會在集合上使用 Single 方法。 如果集合是空的或是有多個項目,Single
方法會擲回例外狀況。 替代方式是 SingleOrDefault,它會在集合是空的時傳回預設值。 針對此查詢,傳回了預設值中的 null
。
選取課程時,下列程式碼會填入檢視模型的 Enrollments
屬性:
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;
}
更新 Instructors [索引] 頁面
以下列程式碼來更新 Pages/Instructors/Index.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>
}
上述程式碼會進行下列變更:
將
page
指示詞更新為@page "{id:int?}"
。"{id:int?}"
是路由範本。 路由範本將 URL 中的整數查詢字串變更為路由資料。 例如,只有在@page
指示詞產生如下的 URL 時,按一下講師的 [選取] 連結:https://localhost:5001/Instructors?id=2
頁面指示詞是
@page "{id:int?}"
時,URL 為:https://localhost:5001/Instructors/2
新增 [辦公室] 資料行,該資料行只有在
item.OfficeAssignment
不是 Null 時才會顯示item.OfficeAssignment.Location
。 因為這是一對零或一關聯性,所有可能沒有相關的 OfficeAssignment 實體。@if (item.OfficeAssignment != null) { @item.OfficeAssignment.Location }
新增 [課程] 資料行,以顯示每位講師所教授的課程。 若要深入了解此 razor 語法,請參閱明確的行轉換。
新增程式碼,將
class="table-success"
動態新增至所選講師和課程的tr
項目。 這會使用啟動程序類別設定所選取資料列的背景色彩。string selectedRow = ""; if (item.CourseID == Model.CourseID) { selectedRow = "table-success"; } <tr class="@selectedRow">
新增標示為選取的超連結。 此連結會將所選取的講師識別碼傳送至
Index
方法,並設定背景色彩。<a asp-action="Index" asp-route-id="@item.ID">Select</a> |
新增所選講師的課程資料表。
新增所選課程的學生註冊資料表。
執行應用程式並選取 [講師] 索引標籤。此頁面會顯示來自相關 OfficeAssignment
實體的 Location
(辦公室)。 如果 OfficeAssignment
為 Null,則會顯示空的資料表資料格。
按一下講師的 [選取] 連結。 資料列樣式會變更,並會顯示指派給該講師的課程。
選取課程,以查看已註冊學生和其年級的清單。
下一步
下一個教學課程會示範如何更新相關資料。
本教學課程說明如何讀取及顯示相關資料。 相關資料是 EF Core 載入到導覽屬性的資料。
下圖顯示本教學課程的已完成頁面:
積極式、明確式和消極式載入
EF Core 有幾種方式可以將相關資料載入到實體的導覽屬性:
積極式載入。 積極式載入是指某一類型實體的查詢同時也會載入相關實體。 讀取實體時,將會擷取其相關資料。 這通常會導致單一聯結查詢,其可擷取所有需要的資料。 EF Core 將針對某些類型的積極式載入發出多個查詢。 發出多個查詢可能比大型單一查詢更有效率。 積極式載入是使用
Include
和ThenInclude
方法加以指定。當集合導覽包含在內時,積極式載入會傳送多個查詢:
- 針對主查詢傳送一個查詢
- 針對負載樹狀結構中的每個集合「邊緣」傳送一個查詢。
使用
Load
的個別查詢:資料可以在個別查詢中擷取,而 EF Core 會「修正」導覽屬性。 「修正」表示 EF Core 會自動填入導覽屬性。 使用Load
的個別查詢更像是明確式載入,而不是積極式載入。注意:EF Core 會將導覽屬性自動修正為先前已載入至內容執行個體的任何其他實體。 即使「未」明確包含導覽屬性的資料,如果先前已載入某些或所有相關實體,仍然可能會填入該屬性。
明確式載入。 第一次讀取實體時,不會擷取相關資料。 必須撰寫程式碼,才能在需要時擷取相關資料。 使用個別查詢的明確式載入會導致多個查詢傳送至資料庫。 透過明確式載入,程式碼會指定要載入的導覽屬性。 請使用
Load
方法來執行明確式載入。 例如:消極式載入。 第一次讀取實體時,不會擷取相關資料。 第一次存取導覽屬性時,將會自動擷取該導覽屬性所需的資料。 每當第一次存取導覽屬性時,查詢會傳送至資料庫。 延遲載入可能會損害效能,例如當開發人員使用 N+1 模式來載入父系並列舉子系時。
建立 Course 頁面
Course
實體包含導覽屬性,其中包含相關的 Department
實體。
若要顯示針對課程指派的部門名稱:
- 將相關的
Department
實體載入Course.Department
導覽屬性。 - 從
Department
實體的Name
屬性取得名稱。
Scaffold Course 頁面
遵循 Scaffold Student 頁面中的指示,下列部分除外:
- 建立 Pages/Courses 資料夾。
- 使用
Course
作為模型類別。 - 使用現有內容類別,而非建立新的類別。
開啟
Pages/Courses/Index.cshtml.cs
並檢查OnGetAsync
方法。 Scaffolding 引擎已針對Department
導覽屬性指定積極式載入。Include
方法可指定積極式載入。執行應用程式並選取課程連結。 部門資料行便會顯示沒有用的
DepartmentID
。
顯示部門名稱
使用下列程式碼更新 Pages/Courses/Index.cshtml.cs:
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();
}
}
}
上述程式碼會將 Course
屬性變更為 Courses
,並新增 AsNoTracking
。 AsNoTracking
可改善效能,因為不會追蹤傳回的實體。 無需追蹤實體的原因是它們不會在目前內容中更新。
以下列程式碼來更新 Pages/Courses/Index.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>
已對包含 Scaffold 的程式碼進行下列變更:
已將
Course
屬性名稱變更為Courses
。新增顯示
CourseID
屬性值的 [編號] 資料行。 主索引鍵預設不會進行 Scaffold,因為它們對終端使用者通常沒有任何意義。 不過,在此情況下,主索引鍵有意義。變更 [部門] 資料行來顯示部門名稱。 此程式碼會顯示已載入到
Department
導覽屬性之Department
實體的Name
屬性:@Html.DisplayFor(modelItem => item.Department.Name)
執行應用程式,並選取 [Courses] 索引標籤來查看含有部門名稱的清單。
使用 Select 載入相關資料
OnGetAsync
方法使用 Include
方法載入相關資料。 Select
方法是一種替代方案,只會載入所需的相關資料。 如果是單一項目 (例如 Department.Name
),它會使用 SQL INNER JOIN。 如果是集合,它會使用另一種資料庫存取,但集合上的 Include
運算子也是如此。
下列程式碼使用 Select
方法載入相關資料:
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();
}
上述程式碼不會傳回任何實體類型,因此不會進行任何追蹤。 如需 EF 追蹤的詳細資訊,請參閱追蹤與不追蹤的查詢。
CourseViewModel
:
public class CourseViewModel
{
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public string DepartmentName { get; set; }
}
如需完整範例,請參閱 IndexSelect.cshtml 和 IndexSelect.cshtml.cs。
建立 Instructor 頁面
本節會 scaffold Instructor 頁面,並將相關的課程和註冊新增至 Instructors 索引頁面。
此頁面將以下列方式讀取和顯示相關資料:
- 講師清單會顯示來自
OfficeAssignment
實體 (上述映像中的 Office) 的相關資料。Instructor
與OfficeAssignment
實體具有一對零或一關聯性。 積極式載入用於OfficeAssignment
實體。 若需要顯示相關資料,積極式載入通常更有效率。 在此情況下,將會顯示講師的辦公室指派。 - 當使用者選取講師時,將會顯示相關的
Course
實體。Instructor
與Course
實體具有多對多關聯性。 將會針對Course
實體和其相關Department
實體使用積極式載入。 在此情況下,個別查詢可能更有效率,因為只需要所選取講師的課程。 這個範例示範如何使用導覽屬性中實體之導覽屬性的積極式載入。 - 當使用者選取課程時,將會顯示來自
Enrollments
實體的相關資料。 在上述映像中,將會顯示學生姓名和年級。Course
與Enrollment
實體具有一對多關聯性。
建立檢視模型
講師頁面會顯示下列三個不同資料表的資料。 需要檢視模型,其包含代表三個資料表的三個屬性。
使用下列程式碼建立 SchoolViewModels/InstructorIndexData.cs
:
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 Instructor 頁面
遵循 Scaffold Students 頁面中的指示,下列部分除外:
- 建立 Pages/Instructors 資料夾。
- 使用
Instructor
作為模型類別。 - 使用現有內容類別,而非建立新的類別。
若要在更新之前查看 Scaffold 頁面的外觀,請執行應用程式並巡覽至 Instructors 頁面。
以下列程式碼來更新 Pages/Instructors/Index.cshtml.cs
:
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;
}
}
}
}
OnGetAsync
方法會針對所選取講師的識別碼接受選擇性的路由資料。
檢查 Pages/Instructors/Index.cshtml.cs
檔案中的查詢:
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();
這個程式碼會針對下列導覽屬性指定積極式載入:
Instructor.OfficeAssignment
Instructor.CourseAssignments
CourseAssignments.Course
Course.Department
Course.Enrollments
Enrollment.Student
請注意 CourseAssignments
和 Course
之 Include
和 ThenInclude
方法的重複。 若要針對 Course
實體的兩個導覽屬性指定積極式載入,則重複是必要的。
下列程式碼會在已選取講師 (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);
}
選取的講師會從檢視模型的講師清單中擷取。 檢視模型的 Courses
屬性則使用 Course
實體從該講師的 CourseAssignments
導覽屬性載入。
Where
方法會傳回集合。 但是在此情況下,篩選條件會選取單一實體,因此會呼叫 Single
方法,將集合轉換成單一 Instructor
實體。 Instructor
實體提供對 CourseAssignments
屬性的存取。 CourseAssignments
提供對相關 Course
實體的存取。
當集合只有一個項目時,將會在集合上使用 Single
方法。 如果集合是空的或是有多個項目,Single
方法會擲回例外狀況。 替代方式是 SingleOrDefault
,它會在集合是空的時傳回預設值 (在此情況下為 Null)。
選取課程時,下列程式碼會填入檢視模型的 Enrollments
屬性:
if (courseID != null)
{
CourseID = courseID.Value;
var selectedCourse = InstructorData.Courses
.Where(x => x.CourseID == courseID).Single();
InstructorData.Enrollments = selectedCourse.Enrollments;
}
更新 Instructors [索引] 頁面
以下列程式碼來更新 Pages/Instructors/Index.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.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>
}
上述程式碼會進行下列變更:
將
page
指示詞從@page
更新為@page "{id:int?}"
。"{id:int?}"
是路由範本。 路由範本將 URL 中的整數查詢字串變更為路由資料。 例如,只有在@page
指示詞產生如下的 URL 時,按一下講師的 [選取] 連結:https://localhost:5001/Instructors?id=2
頁面指示詞是
@page "{id:int?}"
時,URL 為:https://localhost:5001/Instructors/2
新增 [辦公室] 資料行,該資料行只有在
item.OfficeAssignment
不是 Null 時才會顯示item.OfficeAssignment.Location
。 因為這是一對零或一關聯性,所有可能沒有相關的 OfficeAssignment 實體。@if (item.OfficeAssignment != null) { @item.OfficeAssignment.Location }
新增 [課程] 資料行,以顯示每位講師所教授的課程。 若要深入了解此 razor 語法,請參閱明確的行轉換。
新增程式碼,將
class="table-success"
動態新增至所選講師和課程的tr
項目。 這會使用啟動程序類別設定所選取資料列的背景色彩。string selectedRow = ""; if (item.CourseID == Model.CourseID) { selectedRow = "table-success"; } <tr class="@selectedRow">
新增標示為選取的超連結。 此連結會將所選取的講師識別碼傳送至
Index
方法,並設定背景色彩。<a asp-action="Index" asp-route-id="@item.ID">Select</a> |
新增所選講師的課程資料表。
新增所選課程的學生註冊資料表。
執行應用程式並選取 [講師] 索引標籤。此頁面會顯示來自相關 OfficeAssignment
實體的 Location
(辦公室)。 如果 OfficeAssignment
為 Null,則會顯示空的資料表資料格。
按一下講師的 [選取] 連結。 資料列樣式會變更,並會顯示指派給該講師的課程。
選取課程,以查看已註冊學生和其年級的清單。
使用 Single
Single
方法可以傳入 Where
條件,而不是個別呼叫 Where
方法:
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;
}
}
使用 Single
搭配 Where 條件是個人喜好設定。 其不會對使用 Where
方法提供任何益處。
明確式載入
目前程式碼針對 Enrollments
和 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();
假設使用者很少會想要查看課程中的註冊項目。 在此情況下,最佳方法就是在要求時,只載入註冊資料。 在本節中,OnGetAsync
更新為針對 Enrollments
和 Students
使用明確式載入。
以下列程式碼來更新 Pages/Instructors/Index.cshtml.cs
。
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;
}
}
}
}
上述程式碼會捨棄註冊和學生資料的 ThenInclude 方法呼叫。 如果選取了課程,則明確載入的程式碼會擷取:
- 所選取課程的
Enrollment
實體。 - 每個
Enrollment
的Student
實體。
請注意,上述程式碼會將 .AsNoTracking()
標記為註解。 針對所追蹤的實體,導覽屬性只能進行明確式載入。
測試應用程式。 就使用者的觀點而言,應用程式行為與先前版本相同。
下一步
下一個教學課程會示範如何更新相關資料。
在本教學課程中,將會讀取和顯示相關資料。 相關資料是 EF Core 載入到導覽屬性的資料。
若您遇到無法解決的問題,請下載或檢視完整應用程式。下載指示。
下圖顯示本教學課程的已完成頁面:
相關資料的積極式、明確式和消極式載入
EF Core 有幾種方式可以將相關資料載入到實體的導覽屬性:
積極式載入。 積極式載入是指某一類型實體的查詢同時也會載入相關實體。 讀取實體時,將會擷取其相關資料。 這通常會導致單一聯結查詢,其可擷取所有需要的資料。 EF Core 將針對某些類型的積極式載入發出多個查詢。 相較於 EF6 中的某些查詢只有單一查詢的情況,發出多個查詢可能更有效率。 積極式載入是使用
Include
和ThenInclude
方法加以指定。當集合導覽包含在內時,積極式載入會傳送多個查詢:
- 針對主查詢傳送一個查詢
- 針對負載樹狀結構中的每個集合「邊緣」傳送一個查詢。
使用
Load
的個別查詢:資料可以在個別查詢中擷取,而 EF Core 會「修正」導覽屬性。 「修正」表示 EF Core 會自動填入導覽屬性。 使用Load
的個別查詢更像是明確式載入,而不是積極式載入。注意:EF Core 會將導覽屬性自動修正為先前已載入至內容執行個體的任何其他實體。 即使「未」明確包含導覽屬性的資料,如果先前已載入某些或所有相關實體,仍然可能會填入該屬性。
明確式載入。 第一次讀取實體時,不會擷取相關資料。 必須撰寫程式碼,才能在需要時擷取相關資料。 使用個別查詢的明確式載入會導致多個查詢傳送至資料庫。 透過明確式載入,程式碼會指定要載入的導覽屬性。 請使用
Load
方法來執行明確式載入。 例如:消極式載入。 EF Core 已在 2.1 版中新增消極式載入。 第一次讀取實體時,不會擷取相關資料。 第一次存取導覽屬性時,將會自動擷取該導覽屬性所需的資料。 每當第一次存取導覽屬性時,查詢會傳送至資料庫。
Select
運算子只會載入所需的相關資料。
建立顯示部門名稱的 Course 頁面
Course 實體包含導覽屬性,其中包含 Department
實體。 Department
實體包含已指派課程的部門。
若要在課程清單中顯示所指派部門的名稱:
- 從
Department
實體取得Name
屬性。 Department
實體來自Course.Department
導覽屬性。
Scaffold Course 模型
請遵循建立學生結構模型中的指示,並為模型類別使用 Course
。
上述命令會 Scaffold Course
模型。 在 Visual Studio 中開啟專案。
開啟 Pages/Courses/Index.cshtml.cs
並檢查 OnGetAsync
方法。 Scaffolding 引擎已針對 Department
導覽屬性指定積極式載入。 Include
方法可指定積極式載入。
執行應用程式並選取課程連結。 部門資料行便會顯示沒有用的 DepartmentID
。
以下列程式碼取代 OnGetAsync
方法:
public async Task OnGetAsync()
{
Course = await _context.Courses
.Include(c => c.Department)
.AsNoTracking()
.ToListAsync();
}
上述程式碼會新增 AsNoTracking
。 AsNoTracking
可改善效能,因為不會追蹤傳回的實體。 不會追蹤實體的原因是它們不會在目前的內容中更新。
使用下列醒目提示的標記更新 Pages/Courses/Index.cshtml
:
@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>
已對包含 Scaffold 的程式碼進行下列變更:
已將標題從 Index 變更為 Courses。
新增顯示
CourseID
屬性值的 [編號] 資料行。 主索引鍵預設不會進行 Scaffold,因為它們對終端使用者通常沒有任何意義。 不過,在此情況下,主索引鍵有意義。變更 [部門] 資料行來顯示部門名稱。 此程式碼會顯示已載入到
Department
導覽屬性之Department
實體的Name
屬性:@Html.DisplayFor(modelItem => item.Department.Name)
執行應用程式,並選取 [Courses] 索引標籤來查看含有部門名稱的清單。
使用 Select 載入相關資料
OnGetAsync
方法使用 Include
方法載入相關資料:
public async Task OnGetAsync()
{
Course = await _context.Courses
.Include(c => c.Department)
.AsNoTracking()
.ToListAsync();
}
Select
運算子只會載入所需的相關資料。 如果是單一項目 (例如 Department.Name
),它會使用 SQL INNER JOIN。 如果是集合,它會使用另一種資料庫存取,但集合上的 Include
運算子也是如此。
下列程式碼使用 Select
方法載入相關資料:
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; }
}
如需完整範例,請參閱 IndexSelect.cshtml 和 IndexSelect.cshtml.cs。
建立顯示「課程」和「註冊」的 Instructors 頁面
在本節中,將會建立 Instructors 頁面。
此頁面將以下列方式讀取和顯示相關資料:
- 講師清單會顯示來自
OfficeAssignment
實體 (上述映像中的 Office) 的相關資料。Instructor
與OfficeAssignment
實體具有一對零或一關聯性。 積極式載入用於OfficeAssignment
實體。 若需要顯示相關資料,積極式載入通常更有效率。 在此情況下,將會顯示講師的辦公室指派。 - 當使用者選取講師 (上述映像中的 Harui) 時,便會顯示相關的
Course
實體。Instructor
與Course
實體具有多對多關聯性。 將會針對Course
實體和其相關Department
實體使用積極式載入。 在此情況下,個別查詢可能更有效率,因為只需要所選取講師的課程。 這個範例示範如何使用導覽屬性中實體之導覽屬性的積極式載入。 - 當使用者選取課程 (上述映像中的 Chemistry),隨即顯示
Enrollments
中的相關資料。 在上述映像中,將會顯示學生姓名和年級。Course
與Enrollment
實體具有一對多關聯性。
建立 Instructor [索引] 檢視的檢視模型
講師頁面會顯示下列三個不同資料表的資料。 建立的檢視模型會包含三個實體代表三個資料表。
在 SchoolViewModels 資料夾中,使用下列程式碼建立 InstructorIndexData.cs
:
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 Instructor 模型
請遵循建立學生結構模型中的指示,並為模型類別使用 Instructor
。
上述命令會 Scaffold Instructor
模型。
執行應用程式並巡覽至講師頁面。
以下列程式碼取代 Pages/Instructors/Index.cshtml.cs
:
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;
}
}
}
}
OnGetAsync
方法會針對所選取講師的識別碼接受選擇性的路由資料。
檢查 Pages/Instructors/Index.cshtml.cs
檔案中的查詢:
Instructor.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
查詢具有兩個 Include:
OfficeAssignment
:顯示在 Instructors 檢視。CourseAssignments
:它顯示所教授的課程。
更新 Instructors [索引] 頁面
使用下列標記建立 Pages/Instructors/Index.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.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>
上述標記會進行下列變更:
將
page
指示詞從@page
更新為@page "{id:int?}"
。"{id:int?}"
是路由範本。 路由範本將 URL 中的整數查詢字串變更為路由資料。 例如,只有在@page
指示詞產生如下的 URL 時,按一下講師的 [選取] 連結:http://localhost:1234/Instructors?id=2
頁面指示詞是
@page "{id:int?}"
時,先前的 URL 為:http://localhost:1234/Instructors/2
頁面標題是 Instructors。
新增 [辦公室] 資料行,該資料行只有在
item.OfficeAssignment
不是 Null 時才會顯示item.OfficeAssignment.Location
。 因為這是一對零或一關聯性,所有可能沒有相關的 OfficeAssignment 實體。@if (item.OfficeAssignment != null) { @item.OfficeAssignment.Location }
新增 [課程 資料行,以顯示每位講師所教授課程。 若要深入了解此 razor 語法,請參閱明確的行轉換。
新增程式碼,將
class="success"
動態新增至所選取講師的tr
項目。 這會使用啟動程序類別設定所選取資料列的背景色彩。string selectedRow = ""; if (item.CourseID == Model.CourseID) { selectedRow = "success"; } <tr class="@selectedRow">
新增標示為選取的超連結。 此連結會將所選取的講師識別碼傳送至
Index
方法,並設定背景色彩。<a asp-action="Index" asp-route-id="@item.ID">Select</a> |
執行應用程式並選取 [講師] 索引標籤。此頁面會顯示來自相關 OfficeAssignment
實體的 Location
(辦公室)。 如果 OfficeAssignment 是 Null,就會顯示空的資料表資料格。
按一下選取連結。 資料列樣式變更。
新增選取的講師所教授的課程
以下列程式碼來更新 Pages/Instructors/Index.cshtml.cs
中的 OnGetAsync
方法:
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;
}
}
新增 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;
}
}
檢查已更新的查詢:
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();
上述查詢會新增 Department
實體。
下列程式碼會在已選取講師 (id != null
) 時執行。 選取的講師會從檢視模型的講師清單中擷取。 檢視模型的 Courses
屬性則使用 Course
實體從該講師的 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);
}
Where
方法會傳回集合。 在上述 Where
方法中,只會傳回一個單一 Instructor
實體。 Single
方法會將集合轉換成單一 Instructor
實體。 Instructor
實體提供對 CourseAssignments
屬性的存取。 CourseAssignments
提供對相關 Course
實體的存取。
當集合只有一個項目時,將會在集合上使用 Single
方法。 如果集合是空的或是有多個項目,Single
方法會擲回例外狀況。 替代方式是 SingleOrDefault
,它會在集合是空的時傳回預設值 (在此情況下為 Null)。 在空集合上使用 SingleOrDefault
:
- 造成例外狀況 (由於嘗試在 Null 參考上尋找
Courses
屬性)。 - 例外狀況訊息會不太清楚地指出問題的原因。
選取課程時,下列程式碼會填入檢視模型的 Enrollments
屬性:
if (courseID != null)
{
CourseID = courseID.Value;
Instructor.Enrollments = Instructor.Courses.Where(
x => x.CourseID == courseID).Single().Enrollments;
}
將下列標記新增至 Pages/Instructors/Index.cshtml
Razor Page 結尾:
<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>
}
當選取講師時,上述標記會顯示與講師相關的課程。
測試應用程式。 按一下講師頁面上的選取連結。
顯示學生資料
在本節中,應用程式會更新以顯示所選取課程的學生資料。
使用下列程式碼在 Pages/Instructors/Index.cshtml.cs
的 OnGetAsync
方法中更新查詢:
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();
更新 Pages/Instructors/Index.cshtml
。 將下列標記新增至檔案結尾:
@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>
}
上述標記會顯示註冊所選取課程的學生清單。
重新整理頁面,然後選取講師。 選取課程,以查看已註冊學生和其年級的清單。
使用 Single
Single
方法可以傳入 Where
條件,而不是個別呼叫 Where
方法:
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;
}
}
比起使用 Where
,上述 Single
方法並沒有任何優勢。 某些開發人員偏好使用 Single
方法樣式。
明確式載入
目前程式碼針對 Enrollments
和 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();
假設使用者很少會想要查看課程中的註冊項目。 在此情況下,最佳方法就是在要求時,只載入註冊資料。 在本節中,OnGetAsync
更新為針對 Enrollments
和 Students
使用明確式載入。
使用下列程式碼更新 OnGetAsync
:
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;
}
}
上述程式碼會捨棄註冊和學生資料的 ThenInclude 方法呼叫。 如果選取了課程,醒目提示的程式碼就會擷取:
- 所選取課程的
Enrollment
實體。 - 每個
Enrollment
的Student
實體。
請注意,上述程式碼會將 .AsNoTracking()
註解化。 針對所追蹤的實體,導覽屬性只能進行明確式載入。
測試應用程式。 就使用者的觀點而言,應用程式行為與之前的版本相同。
下一個教學課程會示範如何更新相關資料。