第 3 部分,在 ASP.NET Core 中具有 EF Core 的 Razor Pages - 排序、篩選、分頁
作者:Tom Dykstra、Jeremy Likness 和 Jon P Smith
Contoso 大學 Web 應用程式將示範如何使用 EF Core 和 Visual Studio 來建立 Razor Pages Web 應用程式。 如需教學課程系列的資訊,請參閱第一個教學課程。
如果您遇到無法解決的問題,請下載已完成的應用程式,並遵循本教學課程以將程式碼與您所建立的內容進行比較。
本教學課程會將排序、篩選和分頁功能新增至 Students 頁面。
下圖顯示已完成的頁面。 資料行標題為可按式連結,可用以排序資料行。 重覆按一下資料行標題,可在遞增和遞減排序次序之間切換。
新增排序
以下列程式碼取代 Pages/Students/Index.cshtml.cs
中的程式碼來新增排序。
public class IndexModel : PageModel
{
private readonly SchoolContext _context;
public IndexModel(SchoolContext context)
{
_context = context;
}
public string NameSort { get; set; }
public string DateSort { get; set; }
public string CurrentFilter { get; set; }
public string CurrentSort { get; set; }
public IList<Student> Students { get; set; }
public async Task OnGetAsync(string sortOrder)
{
// using System;
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";
IQueryable<Student> studentsIQ = from s in _context.Students
select s;
switch (sortOrder)
{
case "name_desc":
studentsIQ = studentsIQ.OrderByDescending(s => s.LastName);
break;
case "Date":
studentsIQ = studentsIQ.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
studentsIQ = studentsIQ.OrderByDescending(s => s.EnrollmentDate);
break;
default:
studentsIQ = studentsIQ.OrderBy(s => s.LastName);
break;
}
Students = await studentsIQ.AsNoTracking().ToListAsync();
}
}
上述 程式碼:
- 需要新增
using System;
。 - 新增屬性來包含排序參數。
- 將
Student
屬性的名稱變更為Students
。 - 取代
OnGetAsync
方法中的程式碼。
OnGetAsync
方法會從 URL 中的查詢字串接收 sortOrder
參數。 URL 和查詢字串是由錨點標籤協助程式所產生。
sortOrder
參數為 Name
或 Date
。 sortOrder
參數後面可以選擇接著 _desc
來指定遞減順序。 預設排序順序為遞增。
從 Students 連結要求 [索引] 頁面時,將不會有查詢字串。 學生會以遞增姓氏順序顯示。 依姓氏遞增順序是 switch
陳述式中的 default
。 使用者按一下資料行標題連結時,適當的 sortOrder
值將會在查詢字串值中提供。
Razor Page 會以適當的查詢字串值,使用 NameSort
和 DateSort
來設定資料行標題超連結:
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";
程式碼會使用 C# 條件運算子 ?:。 ?:
運算子是一種三元運算子, 其會採用三個運算元。 第一行指定當 sortOrder
為 null 或空白時,NameSort
設為 name_desc
。 如果 sortOrder
不是 null 或空白,則 NameSort
設為空字串。
這兩個陳述式讓頁面能夠設定資料行標題超連結,如下所示:
目前排序次序 | 姓氏超連結 | 日期超連結 |
---|---|---|
姓氏遞增 | 遞減 | ascending |
姓氏遞減 | ascending | ascending |
日期遞增 | ascending | descending |
日期遞減 | ascending | ascending |
這個方法會使用 LINQ to Entities 來指定排序所依據的資料行。 這個程式碼會在 switch 陳述式之前初始化 IQueryable<Student>
,並在 switch 陳述式中將其修改:
IQueryable<Student> studentsIQ = from s in _context.Students
select s;
switch (sortOrder)
{
case "name_desc":
studentsIQ = studentsIQ.OrderByDescending(s => s.LastName);
break;
case "Date":
studentsIQ = studentsIQ.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
studentsIQ = studentsIQ.OrderByDescending(s => s.EnrollmentDate);
break;
default:
studentsIQ = studentsIQ.OrderBy(s => s.LastName);
break;
}
Students = await studentsIQ.AsNoTracking().ToListAsync();
建立或修改 IQueryable
時,不會有任何查詢傳送至資料庫。 直到 IQueryable
物件轉換成集合前,將不會執行查詢。 IQueryable
會藉由呼叫像是 ToListAsync
的方法,轉換成集合。 因此,IQueryable
程式碼會成為一個單一查詢且不會執行,直到下列陳述式產生:
Students = await studentsIQ.AsNoTracking().ToListAsync();
OnGetAsync
可取得使用大量可排序資料行數的詳細資訊。 如需撰寫此功能程式碼的替代方式相關資訊,請參閱本教學課程系列中 MVC 版本的使用動態 LINQ 來簡化程式碼。
將資料行標題超連結新增至 Student 的 [索引] 頁面
以下列程式碼取代 Students/Index.cshtml
中的程式碼。 所做的變更已醒目提示。
@page
@model ContosoUniversity.Pages.Students.IndexModel
@{
ViewData["Title"] = "Students";
}
<h2>Students</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.NameSort">
@Html.DisplayNameFor(model => model.Students[0].LastName)
</a>
</th>
<th>
@Html.DisplayNameFor(model => model.Students[0].FirstMidName)
</th>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.DateSort">
@Html.DisplayNameFor(model => model.Students[0].EnrollmentDate)
</a>
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Students)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
<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>
上述 程式碼:
- 將超連結新增至
LastName
和EnrollmentDate
資料行標題。 - 使用
NameSort
和DateSort
的資訊,以目前排序次序值來設定超連結。 - 將頁面標題從索引變更為 Students。
- 將
Model.Student
變更為Model.Students
。
若要確認該排序運作正常:
- 執行應用程式並選取 [Students] 索引標籤。
- 按一下資料行標題。
新增篩選
將篩選新增至 Students [索引] 頁面:
- 文字輸入框和提交按鈕會新增至 Razor Page。 文字方塊提供名字或姓氏的搜尋字串。
- 頁面模型會更新為使用文字方塊的值。
更新 OnGetAsync 方法
以下列程式碼取代 Students/Index.cshtml.cs
中的程式碼來新增篩選:
public class IndexModel : PageModel
{
private readonly SchoolContext _context;
public IndexModel(SchoolContext context)
{
_context = context;
}
public string NameSort { get; set; }
public string DateSort { get; set; }
public string CurrentFilter { get; set; }
public string CurrentSort { get; set; }
public IList<Student> Students { get; set; }
public async Task OnGetAsync(string sortOrder, string searchString)
{
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";
CurrentFilter = searchString;
IQueryable<Student> studentsIQ = from s in _context.Students
select s;
if (!String.IsNullOrEmpty(searchString))
{
studentsIQ = studentsIQ.Where(s => s.LastName.Contains(searchString)
|| s.FirstMidName.Contains(searchString));
}
switch (sortOrder)
{
case "name_desc":
studentsIQ = studentsIQ.OrderByDescending(s => s.LastName);
break;
case "Date":
studentsIQ = studentsIQ.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
studentsIQ = studentsIQ.OrderByDescending(s => s.EnrollmentDate);
break;
default:
studentsIQ = studentsIQ.OrderBy(s => s.LastName);
break;
}
Students = await studentsIQ.AsNoTracking().ToListAsync();
}
}
上述 程式碼:
- 將
searchString
參數新增至OnGetAsync
方法,並將參數值儲存在CurrentFilter
屬性中。 從文字方塊中接收搜尋字串值文字方塊會在下一節新增。 - 將
Where
子句新增至 LINQ 陳述式。Where
子句只會選取名字或姓氏包含搜尋字串的學生。 有可以搜尋的值,LINQ 陳述式才會執行。
IQueryable 與IEnumerable
程式碼會呼叫 IQueryable
物件上的 Where 方法,且會在伺服器上處理篩選。 在某些情況下,應用程式可能會呼叫 Where
方法在記憶體內部集合上作為擴充方法。 例如,假設 _context.Students
從 EF CoreDbSet
變更為傳回 IEnumerable
集合的存放庫方法。 結果通常都是一樣的,但在某些情況下可能會不同。
例如,.NET Framework 的 Contains
實作,預設會執行區分大小寫的比較。 在 SQL Server,Contains
區分大小寫取決於 SQL Server 執行個體的定序設定。 SQL Server 預設為不區分大小寫。 SQLite 預設為區分大小寫。 可以使用 ToUpper
使測試明確不區分大小寫:
Where(s => s.LastName.ToUpper().Contains(searchString.ToUpper())`
上述程式碼可確保篩選準則不區分大小寫,即使在 IEnumerable
上呼叫 Where
方法或在 SQLite 上執行也一樣。
在 IEnumerable
集合上呼叫 Contains
時,會使用 .NET Core 實作。 在 IQueryable
物件上呼叫 Contains
時,會使用資料庫實作。
基於效能考量,在 IQueryable
上呼叫 Contains
通常是較理想的做法。 透過 IQueryable
,篩選會由資料庫伺服器進行。 如果先建立 IEnumerable
,則必須從資料庫伺服器傳回所有資料列。
呼叫 ToUpper
會使效能降低。 ToUpper
程式碼會將一個函式新增至 TSQL SELECT 陳述式的 WHERE 子句中。 新增的函式會防止最佳化工具使用索引。 假如 SQL 已安裝為不區分大小寫,除非有需要,否則應盡量避免呼叫 ToUpper
。
如需詳細資訊,請參閱 How to use case-insensitive query with Sqlite provider (如何搭配 Sqlite 提供者使用不區分大小寫查詢)。
更新 Razor Page
取代 Pages/Students/Index.cshtml
中的程式碼以新增 [搜尋] 按鈕。
@page
@model ContosoUniversity.Pages.Students.IndexModel
@{
ViewData["Title"] = "Students";
}
<h2>Students</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
<form asp-page="./Index" method="get">
<div class="form-actions no-color">
<p>
Find by name:
<input type="text" name="SearchString" value="@Model.CurrentFilter" />
<input type="submit" value="Search" class="btn btn-primary" /> |
<a asp-page="./Index">Back to full List</a>
</p>
</div>
</form>
<table class="table">
<thead>
<tr>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.NameSort">
@Html.DisplayNameFor(model => model.Students[0].LastName)
</a>
</th>
<th>
@Html.DisplayNameFor(model => model.Students[0].FirstMidName)
</th>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.DateSort">
@Html.DisplayNameFor(model => model.Students[0].EnrollmentDate)
</a>
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Students)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
<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>
上述程式碼會使用 <form>
標籤協助程式新增搜尋文字方塊和按鈕。 <form>
標籤協助程式預設為使用 POST 提交表單資料。 在 POST 中,參數會在 HTTP 訊息本文中傳遞,而不是在 URL 中傳遞。 使用 HTTP GET 時,表單資料會以查詢字串的形式在 URL 中傳遞。 以查詢字串來傳遞資料,可讓使用者為 URL 加上書籤。 W3C 指導方針 建議,只有在動作不會產生更新時才應使用 GET。
測試應用程式:
選取 [Students] 索引標籤並輸入搜尋字串。 如果您使用 SQLite,則只有在您實作先前示範的選擇性
ToUpper
程式碼時,篩選才會不區分大小寫。選取搜尋。
請注意 URL 中包含了搜尋字串。 例如:
https://localhost:5001/Students?SearchString=an
如果頁面已加上書籤,那麼書籤會包含該頁面的 URL 和 SearchString
查詢字串。 form
中的 method="get"
導致查詢字串的產生。
目前,選取資料行標題排序連結時,[搜尋] 方塊中的篩選值將會遺失。 遺失的篩選值會在下一節修正。
新增分頁
在本節中,PaginatedList
類別用來支援分頁。 PaginatedList
類別會使用 Skip
和 Take
陳述式來篩選伺服器上的資料,而不會擷取資料表中的所有資料列。 下圖顯示分頁按鈕。
建立 PaginatedList 類別
在專案資料夾中,以下列程式碼建立 PaginatedList.cs
:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity
{
public class PaginatedList<T> : List<T>
{
public int PageIndex { get; private set; }
public int TotalPages { get; private set; }
public PaginatedList(List<T> items, int count, int pageIndex, int pageSize)
{
PageIndex = pageIndex;
TotalPages = (int)Math.Ceiling(count / (double)pageSize);
this.AddRange(items);
}
public bool HasPreviousPage => PageIndex > 1;
public bool HasNextPage => PageIndex < TotalPages;
public static async Task<PaginatedList<T>> CreateAsync(
IQueryable<T> source, int pageIndex, int pageSize)
{
var count = await source.CountAsync();
var items = await source.Skip(
(pageIndex - 1) * pageSize)
.Take(pageSize).ToListAsync();
return new PaginatedList<T>(items, count, pageIndex, pageSize);
}
}
}
上述程式碼中的 CreateAsync
方法會採用頁面大小和頁面數,並會將適當的 Skip
和 Take
陳述式套用至 IQueryable
。 在 IQueryable
上呼叫 ToListAsync
時,會傳回僅包含所要求頁面的清單。 HasPreviousPage
和 HasNextPage
屬性可用來啟用或停用 Previous 和 Next 分頁按鈕。
CreateAsync
方法為建立 PaginatedList<T>
之用。 建構函式無法建立 PaginatedList<T>
物件,建構函式也無法執行非同步程式碼。
新增頁面大小至設定
新增 PageSize
至appsettings.json
設定檔:
{
"PageSize": 3,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"SchoolContext": "Server=(localdb)\\mssqllocaldb;Database=CU-1;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
新增分頁至 IndexModel
取代 Students/Index.cshtml.cs
中的程式碼以新增分頁。
using ContosoUniversity.Data;
using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace ContosoUniversity.Pages.Students
{
public class IndexModel : PageModel
{
private readonly SchoolContext _context;
private readonly IConfiguration Configuration;
public IndexModel(SchoolContext context, IConfiguration configuration)
{
_context = context;
Configuration = configuration;
}
public string NameSort { get; set; }
public string DateSort { get; set; }
public string CurrentFilter { get; set; }
public string CurrentSort { get; set; }
public PaginatedList<Student> Students { get; set; }
public async Task OnGetAsync(string sortOrder,
string currentFilter, string searchString, int? pageIndex)
{
CurrentSort = sortOrder;
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";
if (searchString != null)
{
pageIndex = 1;
}
else
{
searchString = currentFilter;
}
CurrentFilter = searchString;
IQueryable<Student> studentsIQ = from s in _context.Students
select s;
if (!String.IsNullOrEmpty(searchString))
{
studentsIQ = studentsIQ.Where(s => s.LastName.Contains(searchString)
|| s.FirstMidName.Contains(searchString));
}
switch (sortOrder)
{
case "name_desc":
studentsIQ = studentsIQ.OrderByDescending(s => s.LastName);
break;
case "Date":
studentsIQ = studentsIQ.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
studentsIQ = studentsIQ.OrderByDescending(s => s.EnrollmentDate);
break;
default:
studentsIQ = studentsIQ.OrderBy(s => s.LastName);
break;
}
var pageSize = Configuration.GetValue("PageSize", 4);
Students = await PaginatedList<Student>.CreateAsync(
studentsIQ.AsNoTracking(), pageIndex ?? 1, pageSize);
}
}
}
上述 程式碼:
- 將
Students
屬性的型別從IList<Student>
變更為PaginatedList<Student>
。 - 將頁面索引、目前的
sortOrder
和currentFilter
新增至OnGetAsync
方法簽章。 - 儲存
CurrentSort
屬性中的排列順序。 - 當有新的搜尋字串時,將頁面索引重設為 1。
- 會使用
PaginatedList
類別來取得 Students 實體。 - 從設定中將
pageSize
設定為 3,如果設定失敗,則為 4。
遇下列情況時,OnGetAsync
接收的所有參數都會成為 Null:
- 從 Students 連結呼叫頁面。
- 使用者沒有按一下分頁或排序連結。
按一下分頁連結時,頁面索引變數即包含要顯示的頁面數。
CurrentSort
屬性提供 Razor Page 目前的排列順序。 目前的排序次序必須包含在分頁連結中,以保留分頁時的排序次序。
CurrentFilter
屬性提供 Razor Page 目前的篩選字串。 CurrentFilter
值:
- 必須包含在分頁連結中,以保留分頁時的篩選設定。
- 頁面重新顯示時,必須還原到文字方塊中。
如果搜尋字串在分頁時變更,頁面會重設為 1。 頁面必須重設為 1 是因為新的篩選可能會導致顯示不同的資料。 輸入搜尋值和選取 Submit 時:
- 搜尋字串變更。
searchString
參數不是 null。
PaginatedList.CreateAsync
方法會以支援分頁的集合類型,將學生查詢轉換成學生單一頁面。 該單一頁面的學生會傳遞至 Razor Page。
在 PaginatedList.CreateAsync
呼叫中,pageIndex
之後的兩個問號代表 Null 聯合運算子。 Null 聯合運算子會針對可為 Null 的型別定義一個預設值。 如果運算式有值,則運算式 pageIndex ?? 1
會傳回 pageIndex
的值,否則會傳回 1。
新增分頁連結
將 Students/Index.cshtml
中的程式碼取代為下列程式碼。 所做的變更已醒目提示:
@page
@model ContosoUniversity.Pages.Students.IndexModel
@{
ViewData["Title"] = "Students";
}
<h2>Students</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
<form asp-page="./Index" method="get">
<div class="form-actions no-color">
<p>
Find by name:
<input type="text" name="SearchString" value="@Model.CurrentFilter" />
<input type="submit" value="Search" class="btn btn-primary" /> |
<a asp-page="./Index">Back to full List</a>
</p>
</div>
</form>
<table class="table">
<thead>
<tr>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.NameSort"
asp-route-currentFilter="@Model.CurrentFilter">
@Html.DisplayNameFor(model => model.Students[0].LastName)
</a>
</th>
<th>
@Html.DisplayNameFor(model => model.Students[0].FirstMidName)
</th>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.DateSort"
asp-route-currentFilter="@Model.CurrentFilter">
@Html.DisplayNameFor(model => model.Students[0].EnrollmentDate)
</a>
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Students)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
<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>
@{
var prevDisabled = !Model.Students.HasPreviousPage ? "disabled" : "";
var nextDisabled = !Model.Students.HasNextPage ? "disabled" : "";
}
<a asp-page="./Index"
asp-route-sortOrder="@Model.CurrentSort"
asp-route-pageIndex="@(Model.Students.PageIndex - 1)"
asp-route-currentFilter="@Model.CurrentFilter"
class="btn btn-primary @prevDisabled">
Previous
</a>
<a asp-page="./Index"
asp-route-sortOrder="@Model.CurrentSort"
asp-route-pageIndex="@(Model.Students.PageIndex + 1)"
asp-route-currentFilter="@Model.CurrentFilter"
class="btn btn-primary @nextDisabled">
Next
</a>
資料行頁首連結會使用查詢字串,將目前的搜尋字串傳遞至 OnGetAsync
方法:
<a asp-page="./Index" asp-route-sortOrder="@Model.NameSort"
asp-route-currentFilter="@Model.CurrentFilter">
@Html.DisplayNameFor(model => model.Students[0].LastName)
</a>
分頁按鈕會由標籤協助程式來顯示:
<a asp-page="./Index"
asp-route-sortOrder="@Model.CurrentSort"
asp-route-pageIndex="@(Model.Students.PageIndex - 1)"
asp-route-currentFilter="@Model.CurrentFilter"
class="btn btn-primary @prevDisabled">
Previous
</a>
<a asp-page="./Index"
asp-route-sortOrder="@Model.CurrentSort"
asp-route-pageIndex="@(Model.Students.PageIndex + 1)"
asp-route-currentFilter="@Model.CurrentFilter"
class="btn btn-primary @nextDisabled">
Next
</a>
執行應用程式並巡覽至學生頁面。
- 若要確定分頁運作正常,請以不同排序次序按一下分頁連結。
- 若要驗證分頁的排序和篩選能正確運作,請輸入搜尋字串並嘗試分頁。
群組
本節將建立 [About
] 頁面,其中會顯示每個註冊日期的已註冊學生人數。 此更新會使用群組,並包含下列步驟:
- 建立資料的檢視模型以用於 [
About
] 頁面。 - 更新 [
About
] 頁面以使用檢視模型。
建立檢視模型
建立 Models/SchoolViewModels 資料夾。
以下列程式碼建立 SchoolViewModels/EnrollmentDateGroup.cs
:
using System;
using System.ComponentModel.DataAnnotations;
namespace ContosoUniversity.Models.SchoolViewModels
{
public class EnrollmentDateGroup
{
[DataType(DataType.Date)]
public DateTime? EnrollmentDate { get; set; }
public int StudentCount { get; set; }
}
}
建立 Razor Page
以下列程式碼建立 Pages/About.cshtml
檔案:
@page
@model ContosoUniversity.Pages.AboutModel
@{
ViewData["Title"] = "Student Body Statistics";
}
<h2>Student Body Statistics</h2>
<table>
<tr>
<th>
Enrollment Date
</th>
<th>
Students
</th>
</tr>
@foreach (var item in Model.Students)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
@item.StudentCount
</td>
</tr>
}
</table>
建立頁面模型
以下列程式碼更新 Pages/About.cshtml.cs
檔案:
using ContosoUniversity.Models.SchoolViewModels;
using ContosoUniversity.Data;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ContosoUniversity.Models;
namespace ContosoUniversity.Pages
{
public class AboutModel : PageModel
{
private readonly SchoolContext _context;
public AboutModel(SchoolContext context)
{
_context = context;
}
public IList<EnrollmentDateGroup> Students { get; set; }
public async Task OnGetAsync()
{
IQueryable<EnrollmentDateGroup> data =
from student in _context.Students
group student by student.EnrollmentDate into dateGroup
select new EnrollmentDateGroup()
{
EnrollmentDate = dateGroup.Key,
StudentCount = dateGroup.Count()
};
Students = await data.AsNoTracking().ToListAsync();
}
}
}
LINQ 陳述式會依註冊日期將學生實體組成群組、計算每個群組中的實體數目、將結果儲存在 EnrollmentDateGroup
檢視模型物件的集合中。
執行應用程式並巡覽至 About 頁面。 每個註冊日期的學生人數將會顯示在資料表中。
下一步
在下一個教學課程中,應用程式將會使用移轉來更新資料模型。
在本教學課程中,將新增排序、篩選、分組和分頁功能。
下圖顯示已完成的頁面。 資料行標題為可按式連結,可用以排序資料行。 重覆按一下資料行標題,可切換遞增和遞減排序次序。
若您遭遇到無法解決的問題,請下載完整應用程式。
將排序新增至索引頁面
新增字串至 Students/Index.cshtml.cs
PageModel
來包含排序參數:
public class IndexModel : PageModel
{
private readonly SchoolContext _context;
public IndexModel(SchoolContext context)
{
_context = context;
}
public string NameSort { get; set; }
public string DateSort { get; set; }
public string CurrentFilter { get; set; }
public string CurrentSort { get; set; }
使用下列程式碼更新 Students/Index.cshtml.cs
OnGetAsync
:
public async Task OnGetAsync(string sortOrder)
{
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";
IQueryable<Student> studentIQ = from s in _context.Student
select s;
switch (sortOrder)
{
case "name_desc":
studentIQ = studentIQ.OrderByDescending(s => s.LastName);
break;
case "Date":
studentIQ = studentIQ.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
studentIQ = studentIQ.OrderByDescending(s => s.EnrollmentDate);
break;
default:
studentIQ = studentIQ.OrderBy(s => s.LastName);
break;
}
Student = await studentIQ.AsNoTracking().ToListAsync();
}
上述程式碼會從 URL 中的查詢字串接收 sortOrder
參數。 URL (包括查詢字串) 是由錨點標籤協助程式所產生
sortOrder
參數為 "Name" 或 "Date"。sortOrder
參數後面可以選擇接著 "_desc" 來指定遞減順序。 預設排序順序為遞增。
從 Students 連結要求 [索引] 頁面時,將不會有查詢字串。 學生會以遞增姓氏順序顯示。 在 switch
陳述式中,預設會依姓氏遞增排序 (fall-through 大小寫)。 使用者按一下資料行標題連結時,適當的 sortOrder
值將會在查詢字串值中提供。
Razor Page 會以適當的查詢字串值,使用 NameSort
和 DateSort
來設定資料行標題超連結:
public async Task OnGetAsync(string sortOrder)
{
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";
IQueryable<Student> studentIQ = from s in _context.Student
select s;
switch (sortOrder)
{
case "name_desc":
studentIQ = studentIQ.OrderByDescending(s => s.LastName);
break;
case "Date":
studentIQ = studentIQ.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
studentIQ = studentIQ.OrderByDescending(s => s.EnrollmentDate);
break;
default:
studentIQ = studentIQ.OrderBy(s => s.LastName);
break;
}
Student = await studentIQ.AsNoTracking().ToListAsync();
}
下列程式碼包含 C# 條件式 ?: 運算子:
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";
第一行指定當 sortOrder
為 null 或空白時,將 NameSort
設為 "name_desc"。如果 sortOrder
不是 null 或空白,則 NameSort
設為空字串。
?: operator
也是所謂的三元運算子。
這兩個陳述式讓頁面能夠設定資料行標題超連結,如下所示:
目前排序次序 | 姓氏超連結 | 日期超連結 |
---|---|---|
姓氏遞增 | 遞減 | ascending |
姓氏遞減 | ascending | ascending |
日期遞增 | ascending | descending |
日期遞減 | ascending | ascending |
這個方法會使用 LINQ to Entities 來指定排序所依據的資料行。 這個程式碼會在 switch 陳述式之前初始化 IQueryable<Student>
,並在 switch 陳述式中將其修改:
public async Task OnGetAsync(string sortOrder)
{
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";
IQueryable<Student> studentIQ = from s in _context.Student
select s;
switch (sortOrder)
{
case "name_desc":
studentIQ = studentIQ.OrderByDescending(s => s.LastName);
break;
case "Date":
studentIQ = studentIQ.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
studentIQ = studentIQ.OrderByDescending(s => s.EnrollmentDate);
break;
default:
studentIQ = studentIQ.OrderBy(s => s.LastName);
break;
}
Student = await studentIQ.AsNoTracking().ToListAsync();
}
建立或修改 IQueryable
時,不會有任何查詢傳送至資料庫。 直到 IQueryable
物件轉換成集合前,將不會執行查詢。 IQueryable
會藉由呼叫像是 ToListAsync
的方法,轉換成集合。 因此,IQueryable
程式碼會成為一個單一查詢且不會執行,直到下列陳述式產生:
Student = await studentIQ.AsNoTracking().ToListAsync();
OnGetAsync
可取得使用大量可排序資料行數的詳細資訊。
將資料行標題超連結新增至 Student 的 [索引] 頁面
以下列醒目提示的程式碼取代 Students/Index.cshtml
中的程式碼:
@page
@model ContosoUniversity.Pages.Students.IndexModel
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.NameSort">
@Html.DisplayNameFor(model => model.Student[0].LastName)
</a>
</th>
<th>
@Html.DisplayNameFor(model => model.Student[0].FirstMidName)
</th>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.DateSort">
@Html.DisplayNameFor(model => model.Student[0].EnrollmentDate)
</a>
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Student)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
<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>
上述 程式碼:
- 將超連結新增至
LastName
和EnrollmentDate
資料行標題。 - 使用
NameSort
和DateSort
的資訊,以目前排序次序值來設定超連結。
若要確認該排序運作正常:
- 執行應用程式並選取 [Students] 索引標籤。
- 按一下 [姓氏]。
- 按一下 [註冊日期]。
若要更深入了解這個程式碼:
- 在
Students/Index.cshtml.cs
,在switch (sortOrder)
上設定中斷點。 - 為
NameSort
和DateSort
新增監看式。 - 在
Students/Index.cshtml
,在@Html.DisplayNameFor(model => model.Student[0].LastName)
上設定中斷點。
逐步執行偵錯工具。
將搜尋方塊新增至 Students [索引] 頁面
將篩選新增至 Students [索引] 頁面:
- 文字輸入框和提交按鈕會新增至 Razor Page。 文字方塊提供名字或姓氏的搜尋字串。
- 頁面模型會更新為使用文字方塊的值。
將篩選功能新增至 Index 方法
使用下列程式碼更新 Students/Index.cshtml.cs
OnGetAsync
:
public async Task OnGetAsync(string sortOrder, string searchString)
{
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";
CurrentFilter = searchString;
IQueryable<Student> studentIQ = from s in _context.Student
select s;
if (!String.IsNullOrEmpty(searchString))
{
studentIQ = studentIQ.Where(s => s.LastName.Contains(searchString)
|| s.FirstMidName.Contains(searchString));
}
switch (sortOrder)
{
case "name_desc":
studentIQ = studentIQ.OrderByDescending(s => s.LastName);
break;
case "Date":
studentIQ = studentIQ.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
studentIQ = studentIQ.OrderByDescending(s => s.EnrollmentDate);
break;
default:
studentIQ = studentIQ.OrderBy(s => s.LastName);
break;
}
Student = await studentIQ.AsNoTracking().ToListAsync();
}
上述 程式碼:
- 將
searchString
參數新增至OnGetAsync
方法。 從文字方塊中接收搜尋字串值文字方塊會在下一節新增。 - 將
Where
子句新增至 LINQ 陳述式。Where
子句只會選取名字或姓氏包含搜尋字串的學生。 有可以搜尋的值,LINQ 陳述式才會執行。
注意:上述程式碼會在 IQueryable
物件上呼叫 Where
方法,而篩選是由伺服器處理。 在某些情況下,應用程式可能會呼叫 Where
方法在記憶體內部集合上作為擴充方法。 例如,假設 _context.Students
從 EF CoreDbSet
變更為傳回 IEnumerable
集合的存放庫方法。 結果通常都是一樣的,但在某些情況下可能會不同。
例如,.NET Framework 的 Contains
實作,預設會執行區分大小寫的比較。 在 SQL Server,Contains
區分大小寫取決於 SQL Server 執行個體的定序設定。 SQL Server 預設為不區分大小寫。 可以使用 ToUpper
使測試明確不區分大小寫:
Where(s => s.LastName.ToUpper().Contains(searchString.ToUpper())
如果程式碼變更為使用 IEnumerable
,則上述程式碼會確保結果不區分大小寫。 在 IEnumerable
集合上呼叫 Contains
時,會使用 .NET Core 實作。 在 IQueryable
物件上呼叫 Contains
時,會使用資料庫實作。 從存放庫傳回 IEnumerable
可能對效能產生明顯的負面影響:
- 所有列都會從資料庫伺服器傳回。
- 在應用程式中,傳回的所有資料列都會套用篩選。
呼叫 ToUpper
會使效能降低。 ToUpper
程式碼會將一個函式新增至 TSQL SELECT 陳述式的 WHERE 子句中。 新增的函式會防止最佳化工具使用索引。 假如 SQL 已安裝為不區分大小寫,除非有需要,否則應盡量避免呼叫 ToUpper
。
將搜尋方塊新增至學生的 [索引] 頁面
在 Pages/Students/Index.cshtml
,新增下列醒目標示的程式碼,以建立 [搜尋] 按鈕和各式各樣的色彩。
@page
@model ContosoUniversity.Pages.Students.IndexModel
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
<form asp-page="./Index" method="get">
<div class="form-actions no-color">
<p>
Find by name:
<input type="text" name="SearchString" value="@Model.CurrentFilter" />
<input type="submit" value="Search" class="btn btn-default" /> |
<a asp-page="./Index">Back to full List</a>
</p>
</div>
</form>
<table class="table">
上述程式碼會使用 <form>
標籤協助程式新增搜尋文字方塊和按鈕。 <form>
標籤協助程式預設為使用 POST 提交表單資料。 在 POST 中,參數會在 HTTP 訊息本文中傳遞,而不是在 URL 中傳遞。 使用 HTTP GET 時,表單資料會以查詢字串的形式在 URL 中傳遞。 以查詢字串來傳遞資料,可讓使用者為 URL 加上書籤。 W3C 指導方針 建議,只有在動作不會產生更新時才應使用 GET。
測試應用程式:
- 選取 [Students] 索引標籤並輸入搜尋字串。
- 選取搜尋。
請注意 URL 中包含了搜尋字串。
http://localhost:5000/Students?SearchString=an
如果頁面已加上書籤,那麼書籤會包含該頁面的 URL 和 SearchString
查詢字串。 form
中的 method="get"
導致查詢字串的產生。
目前,選取資料行標題排序連結時,[搜尋] 方塊中的篩選值將會遺失。 遺失的篩選值會在下一節修正。
將分頁功能新增至 Students 的 [索引] 頁面
在本節中,PaginatedList
類別用來支援分頁。 PaginatedList
類別會使用 Skip
和 Take
陳述式來篩選伺服器上的資料,而不會擷取資料表中的所有資料列。 下圖顯示分頁按鈕。
在專案資料夾中,以下列程式碼建立 PaginatedList.cs
:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity
{
public class PaginatedList<T> : List<T>
{
public int PageIndex { get; private set; }
public int TotalPages { get; private set; }
public PaginatedList(List<T> items, int count, int pageIndex, int pageSize)
{
PageIndex = pageIndex;
TotalPages = (int)Math.Ceiling(count / (double)pageSize);
this.AddRange(items);
}
public bool HasPreviousPage => PageIndex > 1;
public bool HasNextPage => PageIndex < TotalPages;
public static async Task<PaginatedList<T>> CreateAsync(
IQueryable<T> source, int pageIndex, int pageSize)
{
var count = await source.CountAsync();
var items = await source.Skip(
(pageIndex - 1) * pageSize)
.Take(pageSize).ToListAsync();
return new PaginatedList<T>(items, count, pageIndex, pageSize);
}
}
}
上述程式碼中的 CreateAsync
方法會採用頁面大小和頁面數,並會將適當的 Skip
和 Take
陳述式套用至 IQueryable
。 在 IQueryable
上呼叫 ToListAsync
時,會傳回僅包含所要求頁面的清單。 HasPreviousPage
和 HasNextPage
屬性可用來啟用或停用 Previous 和 Next 分頁按鈕。
CreateAsync
方法為建立 PaginatedList<T>
之用。 建構函式無法建立 PaginatedList<T>
物件,建構函式也無法執行非同步程式碼。
將分頁功能新增至 Index 方法
在 Students/Index.cshtml.cs
,將 Student
的型別從 IList<Student>
更新為 PaginatedList<Student>
:
public PaginatedList<Student> Student { get; set; }
使用下列程式碼更新 Students/Index.cshtml.cs
OnGetAsync
:
public async Task OnGetAsync(string sortOrder,
string currentFilter, string searchString, int? pageIndex)
{
CurrentSort = sortOrder;
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";
if (searchString != null)
{
pageIndex = 1;
}
else
{
searchString = currentFilter;
}
CurrentFilter = searchString;
IQueryable<Student> studentIQ = from s in _context.Student
select s;
if (!String.IsNullOrEmpty(searchString))
{
studentIQ = studentIQ.Where(s => s.LastName.Contains(searchString)
|| s.FirstMidName.Contains(searchString));
}
switch (sortOrder)
{
case "name_desc":
studentIQ = studentIQ.OrderByDescending(s => s.LastName);
break;
case "Date":
studentIQ = studentIQ.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
studentIQ = studentIQ.OrderByDescending(s => s.EnrollmentDate);
break;
default:
studentIQ = studentIQ.OrderBy(s => s.LastName);
break;
}
int pageSize = 3;
Student = await PaginatedList<Student>.CreateAsync(
studentIQ.AsNoTracking(), pageIndex ?? 1, pageSize);
}
上述程式碼將頁面索引、目前的 sortOrder
、currentFilter
新增至方法簽章。
public async Task OnGetAsync(string sortOrder,
string currentFilter, string searchString, int? pageIndex)
遇下列情況時,所有的參數都會成為 null:
- 從 Students 連結呼叫頁面。
- 使用者沒有按一下分頁或排序連結。
按一下分頁連結時,頁面索引變數即包含要顯示的頁面數。
CurrentSort
提供 Razor Page 目前的排列順序。 目前的排序次序必須包含在分頁連結中,以保留分頁時的排序次序。
CurrentFilter
提供 Razor Page 目前的篩選字串。 CurrentFilter
值:
- 必須包含在分頁連結中,以保留分頁時的篩選設定。
- 頁面重新顯示時,必須還原到文字方塊中。
如果搜尋字串在分頁時變更,頁面會重設為 1。 頁面必須重設為 1 是因為新的篩選可能會導致顯示不同的資料。 輸入搜尋值和選取 Submit 時:
- 搜尋字串變更。
searchString
參數不是 null。
if (searchString != null)
{
pageIndex = 1;
}
else
{
searchString = currentFilter;
}
PaginatedList.CreateAsync
方法會以支援分頁的集合類型,將學生查詢轉換成學生單一頁面。 該單一頁面的學生會傳遞至 Razor Page。
Student = await PaginatedList<Student>.CreateAsync(
studentIQ.AsNoTracking(), pageIndex ?? 1, pageSize);
在 PaginatedList.CreateAsync
中的兩個問號代表 null 聯合運算子。 Null 聯合運算子會針對可為 Null 的型別定義一個預設值。 運算式 (pageIndex ?? 1)
表示,如果 pageIndex
有一個值就將該值傳回。 如果 pageIndex
沒有值,則傳回 1。
新增分頁連結至學生 Razor Page
更新 Students/Index.cshtml
中的標記。 所做的變更已醒目提示:
@page
@model ContosoUniversity.Pages.Students.IndexModel
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
<form asp-page="./Index" method="get">
<div class="form-actions no-color">
<p>
<label>Find by name: <input type="text" name="SearchString" value="@Model.CurrentFilter" /></label>
<input type="submit" value="Search" class="btn btn-default" /> |
<a asp-page="./Index">Back to full List</a>
</p>
</div>
</form>
<table class="table">
<thead>
<tr>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.NameSort"
asp-route-currentFilter="@Model.CurrentFilter">
@Html.DisplayNameFor(model => model.Student[0].LastName)
</a>
</th>
<th>
@Html.DisplayNameFor(model => model.Student[0].FirstMidName)
</th>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.DateSort"
asp-route-currentFilter="@Model.CurrentFilter">
@Html.DisplayNameFor(model => model.Student[0].EnrollmentDate)
</a>
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Student)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
<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>
@{
var prevDisabled = !Model.Student.HasPreviousPage ? "disabled" : "";
var nextDisabled = !Model.Student.HasNextPage ? "disabled" : "";
}
<a asp-page="./Index"
asp-route-sortOrder="@Model.CurrentSort"
asp-route-pageIndex="@(Model.Student.PageIndex - 1)"
asp-route-currentFilter="@Model.CurrentFilter"
class="btn btn-default @prevDisabled">
Previous
</a>
<a asp-page="./Index"
asp-route-sortOrder="@Model.CurrentSort"
asp-route-pageIndex="@(Model.Student.PageIndex + 1)"
asp-route-currentFilter="@Model.CurrentFilter"
class="btn btn-default @nextDisabled">
Next
</a>
資料行頁首連結會使用查詢字串,將目前的搜尋字串傳遞至 OnGetAsync
方法,讓使用者可以在篩選結果內排序:
<a asp-page="./Index" asp-route-sortOrder="@Model.NameSort"
asp-route-currentFilter="@Model.CurrentFilter">
@Html.DisplayNameFor(model => model.Student[0].LastName)
</a>
分頁按鈕會由標籤協助程式來顯示:
<a asp-page="./Index"
asp-route-sortOrder="@Model.CurrentSort"
asp-route-pageIndex="@(Model.Student.PageIndex - 1)"
asp-route-currentFilter="@Model.CurrentFilter"
class="btn btn-default @prevDisabled">
Previous
</a>
<a asp-page="./Index"
asp-route-sortOrder="@Model.CurrentSort"
asp-route-pageIndex="@(Model.Student.PageIndex + 1)"
asp-route-currentFilter="@Model.CurrentFilter"
class="btn btn-default @nextDisabled">
Next
</a>
執行應用程式並巡覽至學生頁面。
- 若要確定分頁運作正常,請以不同排序次序按一下分頁連結。
- 若要驗證分頁的排序和篩選能正確運作,請輸入搜尋字串並嘗試分頁。
若要更深入了解這個程式碼:
- 在
Students/Index.cshtml.cs
,在switch (sortOrder)
上設定中斷點。 - 為
NameSort
、DateSort
、CurrentSort
、Model.Student.PageIndex
新增監看式。 - 在
Students/Index.cshtml
,在@Html.DisplayNameFor(model => model.Student[0].LastName)
上設定中斷點。
逐步執行偵錯工具。
更新 About 頁面以顯示學生統計資料
在此步驟中,Pages/About.cshtml
會更新為顯示在每一個註冊日期中,共有多少學生註冊。 此更新會使用群組,並包含下列步驟:
- 為 About 頁面所使用的資料,建立檢視模型。
- 更新 About 頁面以使用檢視模型。
建立檢視模型
在 Models 資料夾中建立 SchoolViewModels 資料夾。
在 SchoolViewModels 資料夾中,以下列程式碼新增 EnrollmentDateGroup.cs
:
using System;
using System.ComponentModel.DataAnnotations;
namespace ContosoUniversity.Models.SchoolViewModels
{
public class EnrollmentDateGroup
{
[DataType(DataType.Date)]
public DateTime? EnrollmentDate { get; set; }
public int StudentCount { get; set; }
}
}
更新 About 頁面模型
ASP.NET Core 2.2 中的 Web 範本不包括 [關於] 頁面。 若您使用 ASP.NET Core 2.2,請建立 [關於 Razor Page]。
以下列程式碼更新 Pages/About.cshtml.cs
檔案:
using ContosoUniversity.Models.SchoolViewModels;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ContosoUniversity.Models;
namespace ContosoUniversity.Pages
{
public class AboutModel : PageModel
{
private readonly SchoolContext _context;
public AboutModel(SchoolContext context)
{
_context = context;
}
public IList<EnrollmentDateGroup> Student { get; set; }
public async Task OnGetAsync()
{
IQueryable<EnrollmentDateGroup> data =
from student in _context.Student
group student by student.EnrollmentDate into dateGroup
select new EnrollmentDateGroup()
{
EnrollmentDate = dateGroup.Key,
StudentCount = dateGroup.Count()
};
Student = await data.AsNoTracking().ToListAsync();
}
}
}
LINQ 陳述式會依註冊日期將學生實體組成群組、計算每個群組中的實體數目、將結果儲存在 EnrollmentDateGroup
檢視模型物件的集合中。
修改關於 Razor Page
以下列程式碼取代 Pages/About.cshtml
檔案中的程式碼:
@page
@model ContosoUniversity.Pages.AboutModel
@{
ViewData["Title"] = "Student Body Statistics";
}
<h2>Student Body Statistics</h2>
<table>
<tr>
<th>
Enrollment Date
</th>
<th>
Students
</th>
</tr>
@foreach (var item in Model.Student)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
@item.StudentCount
</td>
</tr>
}
</table>
執行應用程式並巡覽至 About 頁面。 每個註冊日期的學生人數將會顯示在資料表中。
若您遭遇到無法解決的問題,請下載此階段的完整應用程式。
其他資源
在下一個教學課程中,應用程式將會使用移轉來更新資料模型。