EF6与mvc5系列(3):在MVC应用程序中使用EF进行排序,过滤和分页
上节中,我们实现了基本增删查改功能,本节中要在Student的Index页面添加排序,分页和过滤功能,同时创建一个简单的分组页面。
在Student的Index页面添加列排序链接
为了在Index页面中实现排序。修改Index方法中的代码。
在Index方法中添加排序功能
修改Student控制器中的Index方法,在Index视图中添加代码。
// GET: /Student/ public ActionResult Index(string sortOrder) { ViewBag.NameSort = string.IsNullOrEmpty(sortOrder) ? "name_desc" : ""; ViewBag.DateSort=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查询字符串参数,查询字符串的值是由MVC提供,作为一个参数传递给action。参数值是“Name”或者“Date”,然后后面随意跟随_desc用于说明降序排列,默认是正序。
第一次请求Index页面,是没有查询字符串的,页面会呈现出按照Last字段正序查询出的数据。当用户点击列标题超链接的时候,会传递sortOrder的值进行查询。
Index方法使用了LINQ to Entities 指定要排序的列。在switch语句之前创建IQueryable 变量,并在switch语句中修改其值,最后调用ToList()方法。当你创建并修改IQueryable
变量的时候,不向数据库中发送查询请求。当你将IQueryable
对象通过像ToList这样的方法,转换为集合的时候才会执行查询。有关更多动态LINQ,请查看:Dynamic LINQ.
在Index视图页添加列标题超链接
对Index视图代码做如下修改:
<table class="table"> <tr> <th> @Html.ActionLink("LastName", "Index", new { sortOrder=ViewBag.NameSort}) </th> <th> @*@Html.DisplayNameFor(model => model.FirstMidName)*@ FirstMidName </th> <th> @Html.ActionLink("Enrollment Date", "Index", new { sortOrder=ViewBag.DateSort}) </th> <th></th> </tr>
在Index页面添加搜索框
接下来实现页面的搜索功能
在Index方法中添加过滤功能
修改Index方法如下:
// GET: /Student/ public ActionResult Index(string sortOrder,string searchStr) { ViewBag.NameSort = string.IsNullOrEmpty(sortOrder) ? "name_desc" : ""; ViewBag.DateSort=sortOrder=="Date"?"date_desc":"Date"; var students = from s in db.Students select s; if (!string.IsNullOrEmpty(searchStr)) { students = students.Where(s=>s.LastName.Contains(searchStr)||s.FirstMidName.Contains(searchStr)); } 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()); }
在Index方法中添加searchStr参数,此参数来自页面的输入框中写入的值,在where字句中添加LINQ筛选出符合条件的实体。
注意:在很多情况下,可以对EF实体集,或者作为内存中集合的扩展方法调用相同的方法,其作用结果相同。但是有些情况下是不同的。
例如:.NET Framework实现Contains方法的时候,如果传入空字符串会返回数据库中所有行。但是对于SQL Server Compact 4实体框架提供程序,如果输入空字符串不会返回任何行。因此才会将上述中的where字句放在if中以确保不同情况下都能显示数据。同时,.NET Framework实现Contains方法的时候默认是区分大小写的。但是SQL Server Compact 4实体框架提供程序默认不区分大小写。为了避免这种情况,我们可以使用ToUpper方法避免这种情况。稍后的代码中,我们的结果集会返回一个IEnumerable
集合,而不是一个IQueryable
对象。对IEnumerable
集合调用Contains方法的时候,是由.NET Framework实现。而对IQueryable
调用Contains方法的时候,是由数据库提供程序实现的。
不同的数据库应用程序对NULL处理也不同。例如,有时候一个where中包含table.Column != 0
时,不会返回带有NULL的数据行。更多信息请查看: Incorrect handling of null variables in 'where' clause.
Index视图中添加搜索框
如下:
<p> @Html.ActionLink("Create New", "Create") </p> @using(Html.BeginForm()) { <P> Find by Name:@Html.TextBox("searchStr") <input type="submit" value="Search"/> </P> } <table class="table">
注意:.NET Framework实现了Contains方法,当你传入的字符串为空时,返回所有行,但是SQL Server契约4.0的EF提供程序对于这种情况会返回0行数据。就是考虑到这种情况所以将where声明放在If中。.NET Framework对于Contains方法是区分大小写的,但是EF sql server提供程序对于Contains方法是不区分大小写的。对于IEnumerable
集合调用Contains方法会由.NET Framework实现。当对IQueryable对象调用Contains方法,会由数据库提供程序实现。
空处理对于不同的数据库提供程序或者 IQueryable
对象和IEnumerable
集合也是不同的。详情查看: Incorrect handling of null variables in 'where' clause.
添加分页
接下来在Index页面添加分页,我们使用PagedList.Mvc进行分页。我们仅仅在这个例子中使用它,不推荐在其他地方使用其进行分页。
安装PagedList.MVC NuGet包。
工具->库程序包管理器(NuGet程序包管理器)—>程序包管理器控制台->Install-Package PagedList.Mvc
生成项目
在Index方法中添加分页
引入命名空间
using PagedList;
public ActionResult Index(string sortOrder,string searchStr,int? page,string currentFilter) { ViewBag.CurrentSort = sortOrder; //如果是sortOrder为空,返回name_desc,否则返回空字符串。 ViewBag.NameSort = string.IsNullOrEmpty(sortOrder) ? "name_desc" : ""; ViewBag.DateSort=sortOrder=="Date"?"date_desc":"Date"; if (searchStr!=null) { page = 1; } else { searchStr = currentFilter; } ViewBag.CurrentFilter = searchStr; var students = from s in db.Students select s; if (!string.IsNullOrEmpty(searchStr)) { students = students.Where(s=>s.LastName.Contains(searchStr)||s.FirstMidName.Contains(searchStr)); } 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 = 2; int pageNum = (page ?? 1); return View(students.ToPagedList(pageNum,pageSize)); }
1 @*@model IEnumerable<ContosoUniversity.Models.Student>*@ 2 @model PagedList.IPagedList<ContosoUniversity.Models.Student> 3 @using PagedList.Mvc 4 5 6 @{ 7 ViewBag.Title = "Student"; 8 } 9 10 <h2>Student</h2> 11 12 <p> 13 @Html.ActionLink("Create New", "Create") 14 </p> 15 @using(Html.BeginForm("Index","Student",FormMethod.Get)) 16 { 17 <P> 18 Find by Name:@Html.TextBox("searchStr", ViewBag.CurrentFilter as string) 19 <input type="submit" value="Search"/> 20 </P> 21 } 22 <table class="table"> 23 <tr> 24 <th> 25 @Html.ActionLink("LastName", "Index", new { sortOrder = ViewBag.NameSort, currentFilter = ViewBag.CurrentFilter }) 26 </th> 27 <th> 28 @*@Html.DisplayNameFor(model => model.FirstMidName)*@ 29 FirstMidName 30 </th> 31 <th> 32 @Html.ActionLink("Enrollment Date", "Index", new { sortOrder = ViewBag.DateSort, currentFilter = ViewBag.CurrentFilter }) 33 </th> 34 <th></th> 35 </tr> 36 37 @foreach (var item in Model) { 38 <tr> 39 <td> 40 @Html.DisplayFor(modelItem => item.LastName) 41 </td> 42 <td> 43 @Html.DisplayFor(modelItem => item.FirstMidName) 44 </td> 45 <td> 46 @Html.DisplayFor(modelItem => item.EnrollmentDate) 47 </td> 48 <td> 49 @Html.ActionLink("Edit", "Edit", new { id=item.ID }) | 50 @Html.ActionLink("Details", "Details", new { id=item.ID }) | 51 @Html.ActionLink("Delete", "Delete", new { id=item.ID }) 52 </td> 53 </tr> 54 } 55 56 </table> 57 <br /> 58 第 @(Model.PageCount<Model.PageNumber?0:Model.PageNumber) 页 ;共 @Model.PageCount 页 59 @Html.PagedListPager(Model, page => Url.Action("Index", new { page, sortOrder = ViewBag.CurrentSort, currentFilter = ViewBag.CurrentFilter }))
页面首次加载或者用户未点击分页或者分类链接,所有的参数为null,如果点击分页链接,将看到page变量中包含了页码。
ViewBag
属性提供给页面一个当前排序,因为分页时也要保持现有的排序,
ViewBag.CurrentSort = sortOrder;
另一个属性ViewBag.CurrentFilter,向页面提供当前过滤字符串,这样确保分页的时候也保持当前过滤条件。如果分页时查找条件改变,页面就重置为1,因为新的过滤条件查询结果与之前不同。
if (searchStr!=null) { page = 1; } else { searchStr = currentFilter; }
在方法的最后,students的IQueryable
扩展方法:ToPagedList
,将查询结果转为一个分页的学生集合,并传递到视图页。
int pageSize = 3; int pageNumber = (page ?? 1); return View(students.ToPagedList(pageNumber, pageSize));
修改页面的@model,将List对象改为PageList对象。
using声明能够访问MVC的辅助方法。
BeginForm默认提交数据方式为POST,这表示在http消息体中传入的查询字符串参数而不是在URL中传入。当指定HTTP GET,表单数据会以URL作为查询字符串传入这样会使用户看到URL参数,在W3C guidelines for the use of HTTP GET中提到,当Action不是update时推荐使用GET。
使用当前查询字符串初始化文本框。当点击新页面时可以看到当前查询字符串。
Find by Name:@Html.TextBox("searchStr", ViewBag.CurrentFilter as string)
列标题连接使用查询字符串把关键字传递给控制器,这样用户就可以对查询出的结果进行排序。
@Html.ActionLink("LastName", "Index", new { sortOrder = ViewBag.NameSort, currentFilter = ViewBag.CurrentFilter })
显示当前页和总页数
Page @(Model.PageCount < Model.PageNumber ? 0 : Model.PageNumber) of @Model.PageCount
使用PagedListPager
的辅助方法显示分页按钮。
@Html.PagedListPager( Model, page => Url.Action("Index", new { page }) )
创建About页面显示学生统计信息
在这个页面中,将要显示每学期有多少学生入学,这用到了分组和简单的分组计算。接下来将要做:
- 创建需要传递给视图用的模型类
- 修改HomeController中的About方法
- 修改About视图页面
创建视图模型
在项目中添加ViewModels文件夹,在此文件夹中添加类EnrollmentDateGroup.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 public int StudentCount { get; set; } 11 } 12 }
修改HomeController
在Home控制器中添加引用:
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视图
@model IEnumerable< ContosoUniversity.ViewModels.EnrollmentDateGroup> @{ ViewBag.Title = "学生统计"; } <h2>学生统计</h2> <table class="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> <p>Use this area to provide additional information.</p>
运行页面,ok。本节完!