首页 > 代码库 > MVC5 Entity Framework学习之添加排序、筛选和分页功能

MVC5 Entity Framework学习之添加排序、筛选和分页功能

前一篇文章中实现了对Student 实体的的基本CRUD操作,在这篇文章中将演示如何为Students Index页面添加排序、筛选和分页的功能。

下面是当完成排序、筛选和分页功能后的截图,你可以点击列标题来进行排序。


1.为 Students Index页面添加列排序链接

要为Students Index页面添加排序功能,你需要修改Student controller的Index方法,并为Student Index视图添加代码。

向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参数,该参数是"Name"或"Date",使用三元运算符判断并加下划线和"desc"来指名这是一个降序排序,默认是按升序排序。

第一次访问Index页面时是没有查询字符串的,students按照LastName作升序排序显示,这是由switch语句中的default指定的。当用户点击某列的标题链接时,URL的查询字符串中就含有了相应的sortOrder值。

这里使用了两个ViewBag变量以便视图可以将合适的查询字符串提供给列标题链接。

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

这里使用了三元运算符,上面第一个指明假如sortOrder参数为null或为空,则ViewBag.NameSortParm被赋值为"name_desc",否则将其赋值为空。其排序规则为:

Current sort orderLast Name HyperlinkDate Hyperlink
Last Name ascendingdescendingascending
Last Name descendingascendingascending
Date ascendingascendingdescending
Date descendingascendingascending
该方法使用LINQ to Entities来指明要排序的列,上面的代码在switch语句前新建了一个 IQueryable变量,然后在switch中修改它,最后在switch语句后调用ToList方法。当你创建并修改IQueryable变量时,并没有向数据库发送查询命令,直到调用ToList方法将IQueryable 对象转换为一个集合时才真正的执行查询命令。因此,直到return View语句,才会执行查询语句。

作为另一种为每一个排序编写不同的LINQ语句的替代方法,可以使用动态创建LINQ句。

为Student Index视图添加列标题链接


打开Views\Student\Index.cshtml,添加下面的代码

<p>
    @Html.ActionLink("Create New", "Create")
</p>
<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属性使用相应的查询字符串来设置列标题链接的值。

运行项目,点击Last Name和Enrollment Date行标题,验证排序功能



点击Last Name标题,students 将按降序排列


2.向Students Index页面添加搜索框

要在Students Index页面中添加搜索功能,你需要在视图中添加一个文本框和一个提交按钮并修改Index方法,通过在文本框中输入 first name或者last name 来搜索相应的Students数据。

在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.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语句中添加where子句来查询那些first name 或者 last name中包含查询条件的student。查询条件就是你所填写在Index视图的文本框中的字符串,只有当文本框中由查询条件时,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集合中的Contains方法是由 .NET Framework实现的,IQueryable对象中的Contains方法是由数据库提供程序实现的)。

对于不同的数据库提供程序或者使用IQueryable对象作比较或者使用IEnumerable集合作比较时,Null值得处理也是不同的。例如某些情况下,where条件比如table.Column !=0可能不返回那些包含null值得列,

在Student Index 视图中添加一个搜索框


打开Views\Student\Index.cshtml,添加如下代码

<p>
    @Html.ActionLink("Create New", "Create")
</p>

@using (Html.BeginForm())
{
    <p>
        Find by name: @Html.TextBox("SearchString")  
        <input type="submit" value=http://www.mamicode.com/"Search" />

>
运行项目,输入要搜索的值,点击Search验证搜索功能是否正常


注意在URL中并不包含搜索字符串,这意味着如果你将此页面加入书签,然后通过书签来打开此页面,你将无法得到筛选后的结果。

在Students Index页面中添加分页功能

要向Students Index页面中添加分页,你需要安装PagedList.Mvc NuGet包,然后你可以在Index 方法中做一些修改并在Index视图中添加分页链接。PagedList.Mvc是ASP.NET MVC中众多分页和排序包中比较好的一个,这里只是使用它来作为演示,并非作为推荐选择。

安装PagedList.Mvc NuGet包

PagedList包作为PagedList.Mvc包的依赖项会自动安装到项目中,PagedList包为IQueryable和IEnumable集合添加了PagedList集合类型和扩展方法。PagedList扩展方法为从IQueryabl或IEnumable集合产生的PagedList集合数据创建了一个单一的页面,并且PagedList集合提供了多个便于分页的属性和方法。PagedList.Mvc包中含有分页帮助器并可在页面中显示分页按钮。

依次打开Tools-> Library Package Manager-> Package Manager Console

在打开的Package Manager Console中确保Package source是nuget.org 并且Default project是 ContosoUniversity,最后输入Install-Package PagedList.Mvc命令


在Index方法中添加分页功能

打开Controllers\StudentController.cs,添加PagedList命名空间

using PagedList;

使用下面的代码替换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.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:  // 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.CurrentFiler向视图提供当前的搜索条件,该值必须要被包含在分页链接中以保证是针对搜索结果进行分页,同时在页面重新显示时,搜索字符串必须显示在搜索框中。如果在分页时修改了搜索字符串,页码必须要被重置为1,因为新的搜索条件可导致不同的搜索结果。当向搜索框输入搜索字符串并点击提交按钮,这是searchString的值不为null。

if (searchString != null)
{
    page = 1;
}
else
{
    searchString = currentFilter;
}
在该方法的结尾部分,学生IQueryable对象的ToPagedList扩展方法将学生查询转换为一个支持分页的学生集合类型,并将该学生集合传递给视图

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

ToPagedList方法需要一个页码参数,两个问号表示空合并操作符(null-coalescing operator),空合并操作符为nullable 类型指定默认值,表达式(page ?? 1)表示如果page值不为空就返回该值,否则返回1。

向 Student Index视图添加分页链接

打开Views\Student\Index.cshtml,使用下面的代码替换

@model PagedList.IPagedList<ContosoUniversity.Models.Student>
@using PagedList.Mvc;
<link href=http://www.mamicode.com/"~/Content/PagedList.css" rel="stylesheet" type="text/css" />>

页面顶部的@model语句指明视图现在获取的是PagedList对象而不是List对象。

using PagedList.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=http://www.mamicode.com/"Search" />>

默认情况下BeginForm 使用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"

PagedListPager帮助器显示分页按钮

@Html.PagedListPager( Model, page => Url.Action("Index", new { page }) )
PagedListPager帮助器提供了多个选项,你可以自定义URL和样式

运行项目


使用不同的排序方式,并点击分页按钮跳转至不同的页码,确保分页功能正常,然后输入查询条件,再次验证分页和搜索功能正常。



3.创建显示学生统计信息的About页面

要让About页面显示在每一个入学日共有多少学生登记,需要将学生分组并进行简单的计算,要做到这些,需要执行下列操作:

  • 需要为要传递给视图的数据创建一个视图模型类
  • 修改Home控制器中的About方法
  • 修改About视图

创建视图模型

创建一个ViewModels文件夹并添加名为EnrollmentDataGroup.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控制器

打开HomeController.cs,在文件顶部添加using语句引入命名空间

using ContosoUniversity.DAL;
using ContosoUniversity.ViewModels;
为数据库上下文添加类变量

public class HomeController : Controller
{
    private SchoolContext db = new SchoolContext();
使用下面的代码替换About方法

public ActionResult About()
{
    IQueryable<EnrollmentDateGroup> data = http://www.mamicode.com/from student in db.Students>

使用LINQ语句对Student实体按照enrollment date分组,计算每个分组的实体数量,并将结果存储在EnrollmentDateGroup视图模型对象的集合中。

添加Dispose方法

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

修改About视图

打开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>

运行项目,点击About链接,显示学生统计信息



项目源码:https://github.com/johnsonz/MvcContosoUniversity


THE END

MVC5 Entity Framework学习之添加排序、筛选和分页功能