這是微軟官方教程Getting Started with Entity Framework 6 Code First using MVC 5 系列的翻譯,這裏是第三篇:排序、篩選和分頁css
原文:Sorting, Filtering, and Paging with the Entity Framework in an ASP.NET MVC Applicationhtml
譯文版權全部,謝絕全文轉載——但你能夠在你的網站上添加到該教程的連接。git
在以前的教程中你實現了一組使用Web頁面對Student實體的的基本CRUD操做。在本教程中,您將爲索引頁添加排序、篩選和分頁的功能。您還會建立一個簡單的分組頁面。程序員
下圖顯示了當你完成本教程後的頁面截屏。用戶能夠點擊行標題來進行排序,而且屢次點擊可讓你在升序和降序之間切換。github
要爲學生索引頁添加排序功能,你須要往學生控制器中的索引方法和學生索引視圖添加代碼。數據庫
使用下面的代碼替換學生控制器的索引方法:mvc
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"來指示這是一個降序排序。app
索引頁面第一次請求時,沒有任何查詢字符串被傳遞,學生們按照LastName的升序排序顯示。這是switch語句中的default指定的,當用戶點擊某列的標題超連接時,相應的sortOrder值經過查詢字符串傳遞到控制器中。框架
兩個ViewBag變量被用於爲視圖提供合適的查詢字符串值。asp.net
ViewBag.NameSortParm = String.IsNullOrEmpty(sortOrder) ? "name_desc" : ""; ViewBag.DateSortParm = sortOrder == "Date" ? "date_desc" : "Date";
這裏使用了三元選擇語句。第一個指定假如sortOrder參數爲null或爲空,則NameSortParm應設置爲"name_desc",不然將其設置爲空字符串。這兩個語句爲視圖中列標題的超連接提供下列排序規則:
當前排序順序 | Last Name超連接 | Date 超連接 |
Last Name 升序 | 降序 | 升序 |
Last Name 降序 | 升序 | 升序 |
Date 升序 | 升序 | 降序 |
Date 降序 | 升序 | 升序 |
該方法使用LINQ to Entities來指定要做爲排序依據的列。代碼在switch語句前建立了一個 IQueryable變量,而後在switch中修改它,並在switch語句後調用ToList方法。當您建立和修改IQueryable變量時,沒有查詢被實際發送到數據庫執行。直到您將IQueryable 對象經過調用一種方法如ToList轉換爲一個集合時才進行真正的查詢。所以,直到return View語句以前,這段代碼的查詢都不會執行。
做爲爲每一個排序順序編寫不一樣的LINQ語句的替代方法,您能夠動態地建立LINQ句。有關動態LINQ的信息,請參閱Dynamic LINQ。
在Views\Student\Index.cshtml中,使用下面的代碼替換標題行的<tr>和<th>元素。
<tr> <th> @Html.ActionLink("Last Name", "Index", new { sortOrder = ViewBag.NameSortParm}) </th> <th> First Name </th> <th> @Html.ActionLink("Last Name", "Index", new { sortOrder = ViewBag.DateSortParm }) </th> <th></th> </tr>
這段代碼使用ViewBag的屬性來設置超連接和查詢字符串值。
運行該頁面,點擊Last Name和Enrollment Date行標題,觀察排序的變化。
點擊Last Name,學生排序將變爲按照Last Name的降序排列。
要在索引頁中增長搜索功能,你須要向視圖中添加一個文本框及一個提交按鈕並在Index方法中作相應的修改。文本框容許你輸入要在名字和姓氏中檢索的字符串。
在學生控制器中,使用下面的代碼替換Index方法(高亮部分是咱們所作出的修改):
public ActionResult 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.ToUpper().Contains(searchString.ToUpper()) || s.FirstMidName.ToUpper().Contains(searchString.ToUpper())); } 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()); }
您已經將searchString參數添加到Index方法,您也已經添加用於在姓名中搜索指定字符串的Linq語句。搜索字符串是從文本框中接受的,稍後您將在視圖中添加它。只有在搜索字符串有值時,搜索部分的語句纔會執行。
注意:在大部分狀況下,你在實體框架實體集合上或做爲擴展方法去執行同一個的方法結果都是相同的,但某些狀況下可能不一樣。
例如,.Net框架中的Contains方法實現爲當你傳遞一個空字符串做爲參數時,將返回全部行。但實體框架的SQL Server Compact 4.0提供程序將不返回任何行。所以示例中的代碼(將Where語句放入一個if語句中)能夠確保您在全部版本的SQL Server都能獲得相同的結果。此外,Contains方法在.Net框架下默認是執行區分大小寫的比較,而實體框架的SQL Server提供程序在默認狀況下執行不區分大小寫的比較。所以咱們調用ToUpper方法使這裏明確進行不區分大小寫的比較。這樣,未來若是您使用倉儲庫時,能夠不須要進行多餘的代碼修正。那時將返回IEnumerable集合而不是IQueryable對象。(當你在IEnumerable集合上調用Contains方法,你將獲得.Net框架版的Contains實現;當你在IQueryable對象上調用時,你將獲得對應的數據庫提供程序的方法實現。)
當你使用不一樣的數據庫提供程序或使用IQueryable對象和IEnumable集合比較時,空值處理也可能不一樣。例如,在某些狀況下一個where條件好比table.Column !=0可能不會返回包含NULL值的行。更多的信息,請參閱Incorrect handling of null variables in 'where' clause。
在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 class="table"> <tr> <th>
運行索引頁面,輸入搜索字符串並提交,檢查搜索功能是否正常工做。
注意該URL中並不包含搜索字符串,這意味着若是您將查詢結果頁面加入書籤,經過使用書籤打開該頁面將沒法獲得篩選後的列表結果。稍後在本教程中咱們將更改搜索按鈕改用搜索字符串來過濾結果。
要向索引頁面添加分頁,你須要安裝PagedList.Mvc NuGet包,而後你在索引方法及視圖中進行相應的修改。PagedList.Mvc是一個很好的分頁排序包,在此咱們僅使用它來進行演示,下圖顯示附加了分頁鏈接的索引頁面。
安裝PagedList.Mvc NuGet包
PagedList包做爲PagedList.Mvc包的依賴項會自動安裝到項目中。PagedList對IQueryable和IEnumable集合添加了PagedList集合類型及相應的擴展方法。這些擴展方法讓你在使用這些集合時能方便地處理分頁功能。
在程序包管理器控制檯中輸入如下命令來安裝PagedList.Mvc包
Install-Package PagedList.Mvc
在學生控制器中,添加PagedList命名空間:
using PagedList;
使用如下代碼替換Index方法:
public ActionResult 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.ToUpper().Contains(searchString.ToUpper()) || s.FirstMidName.ToUpper().Contains(searchString.ToUpper())); } 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; } int pageSize = 3; int pageNumber = (page ?? 1); return View(students.ToPagedList(pageNumber, pageSize)); }
這段代碼添加了一個page參數,一個當前排序順序參數,添加了一個當前篩選參數到方法簽名中:
public ActionResult Index(string sortOrder, string currentFiler, string searchString, int? page)
頁面第一次顯示時,或者用戶尚未點擊分頁或排序連接,全部參數都爲null。若是點擊分頁連接,page變量將包含要顯示的頁面編號。
一個ViewBag屬性被提供給視圖用於指示當前的排序順序,由於在點擊了分頁連接後必需要保持當前的排序順序才能正確的對結果進行分頁。
ViewBag.CurrentSort = sortOrder;
另外一個屬性ViewBag.CurrentFiler被提供給視圖用於指示當前的搜索字符串,分頁鏈接一樣必須包含此值以保持針對搜索結果進行分頁。同時字符串還必須還原到搜索框中。若是在分頁的過程當中修改了搜索字符串,頁碼被重置爲1,由於新的搜索字符串可能會致使不一樣的搜索結果集合。這一改變是在文本框中輸入值並提交時。在這種狀況下,searchString參數不爲空。
if (searchString != null) { page = 1; } else { searchString = currentFiler; }
在方法的結尾,學生IQueryable對象的ToPagedList擴展方法將學生查詢轉換爲一個包含了單頁的支持分頁的集合類型。該學生集合的單頁被傳遞給視圖用於顯示:
int pageSize = 3; int pageNumber = (page ?? 1); return View(students.ToPagedList(pageNumber, pageSize));
ToPagedList方法須要一個頁碼,兩個問號表示null合成運算符,Null合成運算符定義了可爲空類型的缺省值。在本例中,若是page的值不爲空,則返回該值,若是page的值爲空,則返回1。
在Views\Student\Index.cshtml中,使用下面的代碼替換原來的,高亮部分顯示了咱們所作的更改:
@using PagedList.Mvc; @model PagedList.IPagedList<ContosoUniversity.Models.Student> <link href="~/Content/PagedList.css" type="text/css" rel="stylesheet" /> @{ 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對象。
PagedList.Mvc的using語句使MVC幫助器可使用PagedListPager擴展方法來生成分頁按鈕。
下面的代碼使用了指定了FormMethod.Get的BeginForm的重載。
@using (Html.BeginForm("Index","Student", FormMethod.Get)) { <p> Find By Name: @Html.TextBox("SearchString",ViewBag.CurrentFilter as string) <input type="submit" value="Search" /> </p> }
默認狀況下表單使用POST方式提交數據,這意味着參數使用HTTP消息體傳遞,而不是URL。當你指定使用HTTP GET時,表單數據經過URL來傳遞,這樣用戶就能夠建立該URL的書籤並重復使用。W3C guidelines for the use of HTTP GET推薦你在不會致使結果更新時使用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
若是沒有要顯示的頁,則顯示"Page 0 of 0"。(在這種狀況下頁面數字會大於總頁數,由於PageNumber是1,而PageCount是0)
由PagedListPager幫助器顯示分頁按鈕:
@Html.PagedListPager(Model, page => Url.Action("Index", new { page , sortOrder = ViewBag.CurrentSort , currentFilter = ViewBag.CurrentFilter }))
PagedListPager幫助器提供了不少選項,您能夠自定義Url及樣式,更多的信息請參閱TroyGoode / PagedList。
運行頁面。
點擊不一樣的排序順序,並跳轉到不一樣的頁碼,而後輸入搜索字符串,並再次分頁並驗證排序及搜索過濾還能夠正常工做。
爲Contoso大學的關於頁面添加每日有多少個學生註冊,須要用到分組及簡單的計算,要作到這些,您須要執行下列操做:
在項目文件夾中建立一個ViewModels文件夾,在該文件夾中添加一個新類,命名爲EnrollmentDataGroup.cs,使用下面的代碼替換自動生成的:
1 using System; 2 using System.ComponentModel.DataAnnotations; 3 4 namespace ContosoUniversity.ViewModels 5 { 6 public class EnrollmentDateGroup 7 { 8 [DataType(DataType.Date)] 9 public DateTime? EnrollmentDate { get; set; } 10 11 public int StudentCount { get; set; } 12 } 13 }
在Home控制器中,在文件的頂部添加如下using語句:
using ContosoUniversity.DAL; using ContosoUniversity.ViewModels;
在類定義後添加數據庫上下文的類變量:
public class HomeController : Controller { private SchoolContext db = new SchoolContext();
使用下面的代碼替換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視圖模型對象的集合中。
添加Dispose方法:
protected override void Dispose(bool disposing) { db.Dispose(); base.Dispose(disposing); }
將About.cshtml替換爲以下代碼:
1 @model IEnumerable<ContosoUniversity.ViewModels.EnrollmentDateGroup> 2 @{ 3 ViewBag.Title = "Student Body Statistics"; 4 } 5 <h2>Student Body Statistics</h2> 6 <table> 7 <tr> 8 <th>Enrollment Date</th> 9 <th>Students</th> 10 </tr> 11 @foreach (var item in Model) 12 { 13 <tr> 14 <td>@Html.DisplayFor(o => item.EnrollmentDate)</td> 15 <td>@item.StudentCount</td> 16 </tr> 17 } 18 </table>
運行程序,點擊關於連接,你能夠看到學生的統計信息了。
到目前爲止,你已經實現了基本的CRUD和排序、篩選、分頁及分組功能,下一節中咱們將經過擴展數據模型來介紹更高級的主題。
Tom Dykstra - Tom Dykstra是微軟Web平臺及工具團隊的高級程序員,做家。