首页 > 代码库 > EF6与mvc5系列(3):在MVC应用程序中使用EF进行排序,过滤和分页

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=http://www.mamicode.com/"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=http://www.mamicode.com/"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             FirstMidName30         </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) 页&nbsp;&nbsp;;共 @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 = http://www.mamicode.com/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。本节完!

 

EF6与mvc5系列(3):在MVC应用程序中使用EF进行排序,过滤和分页