共用方式為


教學課程:在 ASP.NET MVC 應用程式中,使用 Entity Framework 新增排序、篩選和分頁

上一個教學課程中,您已針對實體的基本 CRUD 作業 Student 實作一組網頁。 在本教學課程中,您會將排序、篩選和分頁功能新增至 Students Index 頁面。 您也可以建立簡單的群組頁面。

下圖顯示完成時頁面的外觀。 資料行標題是使用者可以按一下以依據該資料行排序的連結。 重覆按一下資料行標題,可切換遞增和遞減排序次序。

Students_Index_page_with_paging

在本教學課程中,您:

  • 新增資料行排序連結
  • 新增 [搜尋] 方塊
  • 新增分頁
  • 建立 [關於] 頁面

必要條件

若要將排序新增至 [學生索引] 頁面,您將變更 Index 控制器的 Student 方法,並將程式碼新增至 Student [索引] 檢視。

將排序功能新增至 Index 方法

  • Controllers\StudentController.cs 中,以下列程式碼取代 Index 方法:

    public ActionResult Index(string sortOrder)
    {
       ViewBag.NameSortParm = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
       ViewBag.DateSortParm = sortOrder == "Date" ? "date_desc" : "Date";
       var students = from s in db.Students
                      select s;
       switch (sortOrder)
       {
          case "name_desc":
             students = students.OrderByDescending(s => s.LastName);
             break;
          case "Date":
             students = students.OrderBy(s => s.EnrollmentDate);
             break;
          case "date_desc":
             students = students.OrderByDescending(s => s.EnrollmentDate);
             break;
          default:
             students = students.OrderBy(s => s.LastName);
             break;
       }
       return View(students.ToList());
    }
    

此程式碼會從 URL 中的查詢字串接收 sortOrder 參數。 查詢字串值是由 ASP.NET MVC 提供,做為動作方法的參數。 參數是字串,可以是 「Name」 或 「Date」,選擇性地後面接著底線和字串 「desc」 來指定遞減順序。 預設排序順序為遞增。

第一次要求 [索引] 頁面時,沒有任何查詢字串。 學生會以 LastName 遞增順序顯示,這是 語句中 switch 後置案例所建立的預設值。 使用者按一下資料行標題超連結時,適當的 sortOrder 值將會在查詢字串值中提供。

使用兩 ViewBag 個變數,讓檢視可以使用適當的查詢字串值來設定資料行標題超連結:

ViewBag.NameSortParm = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
ViewBag.DateSortParm = sortOrder == "Date" ? "date_desc" : "Date";

這些是三元陳述式。 第一個參數指定如果 sortOrder 參數為 null 或空白, ViewBag.NameSortParm 應該設定為 「name_desc」,否則應該設定為空字串。 這兩個陳述式讓檢視能夠設定資料行標題超連結,如下所示:

目前排序次序 姓氏超連結 日期超連結
姓氏遞增 descending ascending
姓氏遞減 ascending ascending
日期遞增 ascending descending
日期遞減 ascending ascending

方法會使用LINQ to Entities來指定要排序的資料行。 程式碼會在 IQueryable<T> 語句之前 switch 建立變數、在 語句中 switch 修改變數,並在 語句之後 switch 呼叫 ToList 方法。 當您建立和修改 IQueryable 變數時,沒有查詢會傳送至資料庫。 除非您藉由呼叫 之類的 ToList 方法,將 IQueryable 物件轉換成集合,否則不會執行查詢。 因此,此程式碼會產生在 語句之前 return View 不會執行的單一查詢。

除了為每個排序次序撰寫不同的 LINQ 語句,您也可以動態建立 LINQ 語句。 如需動態 LINQ 的相關資訊,請參閱 動態 LINQ

  1. Views\Student\Index.cshtml中,以醒目提示的程式碼取代 <tr> 標題列的 和 <th> 元素:

    <p>
        @Html.ActionLink("Create New", "Create")
    </p>
    <table class="table">
        <tr>
            <th>
                @Html.ActionLink("Last Name", "Index", new { sortOrder = ViewBag.NameSortParm })
            </th>
            <th>First Name
            </th>
            <th>
                @Html.ActionLink("Enrollment Date", "Index", new { sortOrder = ViewBag.DateSortParm })
            </th>
            <th></th>
        </tr>
    
    @foreach (var item in Model) {
    

    此程式碼會使用屬性中 ViewBag 的資訊,以適當的查詢字串值來設定超連結。

  2. 執行頁面,然後按一下 [姓氏 ] 和 [ 註冊日期] 資料 行標題,以確認排序可正常運作。

    按一下 [ 姓氏] 標題之後,學生會以遞減姓氏順序顯示。

若要將篩選新增至 Students 索引頁面,您會將文字方塊和提交按鈕新增至檢視,並在 方法中 Index 進行對應的變更。 文字方塊可讓您輸入字串,以在名字和姓氏欄位中搜尋。

將篩選功能新增至 Index 方法

  • Controllers\StudentController.cs 中,將 方法取代 Index 為下列程式碼, (變更會反白顯示) :

    public ViewResult Index(string sortOrder, string searchString)
    {
        ViewBag.NameSortParm = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
        ViewBag.DateSortParm = sortOrder == "Date" ? "date_desc" : "Date";
        var students = from s in db.Students
                       select s;
        if (!String.IsNullOrEmpty(searchString))
        {
            students = students.Where(s => s.LastName.Contains(searchString)
                                   || s.FirstMidName.Contains(searchString));
        }
        switch (sortOrder)
        {
            case "name_desc":
                students = students.OrderByDescending(s => s.LastName);
                break;
            case "Date":
                students = students.OrderBy(s => s.EnrollmentDate);
                break;
            case "date_desc":
                students = students.OrderByDescending(s => s.EnrollmentDate);
                break;
            default:
                students = students.OrderBy(s => s.LastName);
                break;
        }
    
        return View(students.ToList());
    }
    

程式碼會將參數新增 searchStringIndex 方法。 從將新增至 [索引] 檢視的文字方塊中接收搜尋字串值。 它也會將 子句新增 where 至 LINQ 語句,只選取名字或姓氏包含搜尋字串的學生。 只有在有要搜尋的值時,才會執行加入 Where 子句的 語句。

注意

在許多情況下,您可以在 Entity Framework 實體集上呼叫相同的方法,或做為記憶體內部集合上的擴充方法。 結果通常相同,但在某些情況下可能會不同。

例如,方法的.NET Framework實作 Contains 會在您將空字串傳遞給此方法時傳回所有資料列,但 SQL Server Compact 4.0 的 Entity Framework 提供者會傳回空字串的零個數據列。 因此,範例中的程式碼 (將 Where 語句放在 語句內 if ,) 可確保您取得所有版本SQL Server的相同結果。 此外,方法的.NET Framework實作 Contains 預設會執行區分大小寫的比較,但 Entity Framework SQL Server提供者預設會執行不區分大小寫的比較。 因此,呼叫 ToUpper 方法讓測試明確區分大小寫可確保當您稍後變更程式碼以使用存放庫時,結果不會變更,這會傳回 IEnumerable 集合而非 IQueryable 物件。 (當您在 IEnumerable 集合上呼叫 Contains 方法時,將取得 .NET Framework 實作;當您在 IQueryable 物件上呼叫它時,則會取得資料庫提供者實作。)

對於不同的資料庫提供者,或當您使用 IEnumerable 集合時,使用 IQueryable 物件時,Null 處理可能也不同。 例如,在某些情況下, Where 這類 table.Column != 0 條件可能不會傳回具有 null 做為值的資料行。 根據預設,EF 會產生額外的 SQL 運算子,讓 Null 值在資料庫中的運作方式相等,就像它在記憶體中運作一樣,但是您可以在 EF6 中設定 UseDatabaseNullSemantics 旗標,或在 EF Core 中呼叫 UseRelationalNulls 方法來設定此行為。

將搜尋方塊新增至 Student 索引檢視

  1. Views\Student\Index.cshtml中,在開頭 table 標記之前立即新增醒目提示的程式碼,以建立標題、文字方塊和[搜尋]按鈕。

    <p>
        @Html.ActionLink("Create New", "Create")
    </p>
    
    @using (Html.BeginForm())
    {
        <p>
            Find by name: @Html.TextBox("SearchString")  
            <input type="submit" value="Search" /></p>
    }
    
    <table>
        <tr>
    
  2. 執行頁面,輸入搜尋字串,然後按一下 [ 搜尋 ] 以確認篩選正常運作。

    請注意,URL 不包含 「an」 搜尋字串,這表示如果您將此頁面加入書簽,當您使用書簽時,將不會取得篩選的清單。 這也適用于資料行排序連結,因為它們會排序整個清單。 您稍後會在教學課程中變更 [ 搜尋 ] 按鈕,以使用篩選準則的查詢字串。

新增分頁

若要將分頁新增至 Students 索引頁面,您必須先安裝 PagedList.Mvc NuGet 套件。 然後您會在 Index 方法中進行其他變更,並將分頁連結新增至 Index 檢視。 PagedList.Mvc 是 ASP.NET MVC 的許多良好分頁和排序套件之一,此處的使用僅供範例使用,而不是建議用於其他選項。

安裝 PagedList.MVC NuGet 套件

NuGet PagedList.Mvc 套件會自動將 PagedList 套件安裝為相依性。 PagedList套件會 PagedList 安裝 和 IEnumerable 集合的 IQueryable 集合類型和擴充方法。 擴充方法會在 或 IQueryableIEnumerable 的集合中 PagedList 建立單一頁面的資料,而 PagedList 集合會提供數個屬性和方法,以利分頁。 PagedList.Mvc套件會安裝顯示分頁按鈕的分頁協助程式。

  1. 從 [ 工具 ] 功能表中,選取 [NuGet 套件管理員 ],然後選取 [ 套件管理員主控台]。

  2. 在 [套件管理員主控台] 視窗中,確定 [套件來源] 已 nuget.org,而[預設] 專案ContosoUniversity,然後輸入下列命令:

    Install-Package PagedList.Mvc
    
  3. 建置專案。

注意

PageList 套件不再維護。 因此,針對目前的專案,最好是使用 X.PagedList 套件。 主要差異在於 X.PagedList 是可攜式元件。 這表示套件是跨平臺的,而且可用於 Web 專案和其他 .NET 專案。 新的套件不應該造成相容性問題,因為它自 8.4 版以來已移植到 .NET 6。

將分頁功能新增至 Index 方法

  1. Controllers\StudentController.cs中,新增 using 命名空間的 PagedList 語句:

    using PagedList;
    
  2. 以下列程式碼取代 Index 方法:

    public ViewResult Index(string sortOrder, string currentFilter, string searchString, int? page)
    {
       ViewBag.CurrentSort = sortOrder;
       ViewBag.NameSortParm = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
       ViewBag.DateSortParm = sortOrder == "Date" ? "date_desc" : "Date";
    
       if (searchString != null)
       {
          page = 1;
       }
       else
       {
          searchString = currentFilter;
       }
    
       ViewBag.CurrentFilter = searchString;
    
       var students = from s in db.Students
                      select s;
       if (!String.IsNullOrEmpty(searchString))
       {
          students = students.Where(s => s.LastName.Contains(searchString)
                                 || s.FirstMidName.Contains(searchString));
       }
       switch (sortOrder)
       {
          case "name_desc":
             students = students.OrderByDescending(s => s.LastName);
             break;
          case "Date":
             students = students.OrderBy(s => s.EnrollmentDate);
             break;
          case "date_desc":
             students = students.OrderByDescending(s => s.EnrollmentDate);
             break;
          default:  // Name ascending 
             students = students.OrderBy(s => s.LastName);
             break;
       }
    
       int pageSize = 3;
       int pageNumber = (page ?? 1);
       return View(students.ToPagedList(pageNumber, pageSize));
    }
    

    此程式碼會將參數、目前的排序次序參數和目前的篩選參數新增 page 至方法簽章:

    public ActionResult Index(string sortOrder, string currentFilter, string searchString, int? page)
    

    第一次顯示頁面時,或使用者尚未按一下分頁或排序連結,則所有參數都是 Null。 如果按一下分頁連結,變數 page 就會包含要顯示的頁碼。

    ViewBag屬性會提供目前排序次序的檢視,因為這必須包含在分頁連結中,才能在分頁時保持排序次序相同:

    ViewBag.CurrentSort = sortOrder;
    

    另一個 屬性 ViewBag.CurrentFilter 會提供檢視與目前的篩選字串。 此值必須包含在分頁連結中,才能維護分頁期間的篩選設定,而且它必須在頁面重新顯示時還原為文字方塊。 如果搜尋字串在分頁期間變更,頁面必須重設為 1,因為新的篩選可能會導致顯示不同的資料。 在文字方塊中輸入值並按下送出按鈕時,搜尋字串就會變更。 在此情況下,參數 searchString 不是 Null。

    if (searchString != null)
    {
        page = 1;
    }
    else
    {
        searchString = currentFilter;
    }
    

    方法結束時, ToPagedList students IQueryable 物件的擴充方法會將學生查詢轉換成支援分頁之集合類型中的單一學生頁面。 然後,該單一頁面的學生會傳遞至檢視:

    int pageSize = 3;
    int pageNumber = (page ?? 1);
    return View(students.ToPagedList(pageNumber, pageSize));
    

    ToPagedList 方法會採用頁面數。 這兩個問號代表 null 聯合運算子。 Null 聯合運算子將針對可為 Null 的型別定義預設值;(page ?? 1) 運算式表示在它含有值時會傳回值 page,或在 page 為 null 時傳回 1。

  1. Views\Student\Index.cshtml中,以下列程式碼取代現有的程式碼。 所做的變更已醒目提示。

    @model PagedList.IPagedList<ContosoUniversity.Models.Student>
    @using PagedList.Mvc;
    <link href="~/Content/PagedList.css" rel="stylesheet" type="text/css" />
    
    @{
        ViewBag.Title = "Students";
    }
    
    <h2>Students</h2>
    
    <p>
        @Html.ActionLink("Create New", "Create")
    </p>
    @using (Html.BeginForm("Index", "Student", FormMethod.Get))
    {
        <p>
            Find by name: @Html.TextBox("SearchString", ViewBag.CurrentFilter as string)
            <input type="submit" value="Search" />
        </p>
    }
    <table class="table">
        <tr>
            <th>
                @Html.ActionLink("Last Name", "Index", new { sortOrder = ViewBag.NameSortParm, currentFilter=ViewBag.CurrentFilter })
            </th>
            <th>
                First Name
            </th>
            <th>
                @Html.ActionLink("Enrollment Date", "Index", new { sortOrder = ViewBag.DateSortParm, currentFilter=ViewBag.CurrentFilter })
            </th>
            <th></th>
        </tr>
    
    @foreach (var item in Model) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.LastName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.FirstMidName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.EnrollmentDate)
            </td>
            <td>
                @Html.ActionLink("Edit", "Edit", new { id=item.ID }) |
                @Html.ActionLink("Details", "Details", new { id=item.ID }) |
                @Html.ActionLink("Delete", "Delete", new { id=item.ID })
            </td>
        </tr>
    }
    
    </table>
    <br />
    Page @(Model.PageCount < Model.PageNumber ? 0 : Model.PageNumber) of @Model.PageCount
    
    @Html.PagedListPager(Model, page => Url.Action("Index", 
        new { page, sortOrder = ViewBag.CurrentSort, currentFilter = ViewBag.CurrentFilter }))
    

    頁面頂端的 @model 陳述式指定檢視現在會取得 PagedList 物件,而不是 List 物件。

    usingPagedList.Mvc 語句可讓您存取分頁按鈕的 MVC 協助程式。

    此程式碼會使用 BeginForm 的多載,允許它指定 FormMethod.Get

    @using (Html.BeginForm("Index", "Student", FormMethod.Get))
    {
        <p>
            Find by name: @Html.TextBox("SearchString", ViewBag.CurrentFilter as string)  
            <input type="submit" value="Search" />
        </p>
    }
    

    預設 BeginForm 會使用 POST 提交表單資料,這表示參數會傳入 HTTP 訊息本文,而不是在 URL 中做為查詢字串。 當您指定 HTTP GET 時,表單資料會以 URL 中作為查詢字串傳遞,這可讓使用者為該 URL 加上書籤。 使用 HTTP GET 的 W3C 指導方針建議您在動作不會產生更新時使用 GET。

    文字方塊會以目前的搜尋字串初始化,因此當您按一下新頁面時,您可以看到目前的搜尋字串。

    Find by name: @Html.TextBox("SearchString", ViewBag.CurrentFilter as string)
    

    資料行標頭連結會使用查詢字串,將目前的搜尋字串傳遞至控制器,讓使用者可以在篩選結果內排序:

    @Html.ActionLink("Last Name", "Index", new { sortOrder=ViewBag.NameSortParm, currentFilter=ViewBag.CurrentFilter })
    

    目前頁面和頁面總數隨即顯示。

    Page @(Model.PageCount < Model.PageNumber ? 0 : Model.PageNumber) of @Model.PageCount
    

    如果沒有要顯示的頁面,則會顯示「第 0 頁為 0」。 (在此情況下,頁碼大於頁面計數,因為 Model.PageNumber 是 1,且 Model.PageCount 為 0.)

    協助程式會顯示 PagedListPager 分頁按鈕:

    @Html.PagedListPager( Model, page => Url.Action("Index", new { page }) )
    

    協助 PagedListPager 程式提供許多選項供您自訂,包括 URL 和樣式。 如需詳細資訊,請參閱 GitHub 網站上的 TroyGoode / PagedList

  2. 執行頁面。

    以不同排序次序按一下分頁連結,以確定分頁運作正常。 然後輸入搜尋字串並再次嘗試分頁,以確認分頁的排序和篩選能正確運作。

建立 [關於] 頁面

對於 Contoso 大學網站的 About 頁面,您將顯示每個註冊日期已有多少學生註冊。 這需要對群組進行分組和簡單計算。 若要完成此工作,您需要執行下列作業:

  • 針對您需要傳遞至檢視的資料,建立檢視模型類別。
  • About修改控制器中的 Home 方法。
  • 修改檢 About 視。

建立檢視模型

在專案資料夾中建立 ViewModels 資料夾。 在該資料夾中,新增類別檔案 EnrollmentDateGroup.cs ,並以下列程式碼取代範本程式碼:

using System;
using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.ViewModels
{
    public class EnrollmentDateGroup
    {
        [DataType(DataType.Date)]
        public DateTime? EnrollmentDate { get; set; }

        public int StudentCount { get; set; }
    }
}

修改 Home 控制器

  1. HomeController.cs中,在檔案頂端新增下列 using 語句:

    using ContosoUniversity.DAL;
    using ContosoUniversity.ViewModels;
    
  2. 在類別的左大括弧之後,立即為資料庫內容新增類別變數:

    public class HomeController : Controller
    {
        private SchoolContext db = new SchoolContext();
    
  3. 以下列程式碼取代 About 方法:

    public ActionResult About()
    {
        IQueryable<EnrollmentDateGroup> data = from student in db.Students
                   group student by student.EnrollmentDate into dateGroup
                   select new EnrollmentDateGroup()
                   {
                       EnrollmentDate = dateGroup.Key,
                       StudentCount = dateGroup.Count()
                   };
        return View(data.ToList());
    }
    

    LINQ 陳述式會依註冊日期將學生實體組成群組、計算每個群組中的實體數目、將結果儲存在 EnrollmentDateGroup 檢視模型物件的集合中。

  4. Dispose新增方法:

    protected override void Dispose(bool disposing)
    {
        db.Dispose();
        base.Dispose(disposing);
    }
    

修改 About 檢視

  1. 以下列程式碼取代 Views\Home\About.cshtml 檔案中的程式碼:

    @model IEnumerable<ContosoUniversity.ViewModels.EnrollmentDateGroup>
               
    @{
        ViewBag.Title = "Student Body Statistics";
    }
    
    <h2>Student Body Statistics</h2>
    
    <table>
        <tr>
            <th>
                Enrollment Date
            </th>
            <th>
                Students
            </th>
        </tr>
    
    @foreach (var item in Model) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.EnrollmentDate)
            </td>
            <td>
                @item.StudentCount
            </td>
        </tr>
    }
    </table>
    
  2. 執行應用程式,然後按一下 [ 關於 ] 連結。

    每個註冊日期的學生計數會顯示在資料表中。

    About_page

取得程式碼

下載已完成的專案

其他資源

您可以在 ASP.NET 資料存取 - 建議的資源中找到其他 Entity Framework 資源的連結。

後續步驟

在本教學課程中,您:

  • 新增資料行排序連結
  • 新增 [搜尋] 方塊
  • 新增分頁
  • 建立 [關於] 頁面

請前進到下一篇文章,瞭解如何使用連線復原和命令攔截。