• MVC5与EF6结合教程(03):排序、筛选和分页


    原文:https://docs.microsoft.com/zh-cn/aspnet/mvc/overview/getting-started/getting-started-with-ef-using-mvc/sorting-filtering-and-paging-with-the-entity-framework-in-an-asp-net-mvc-application

    上一教程中,你为 Student 实体实现了一组用于基本 CRUD 操作的网页。 在本教程中,您将向学生索引页添加排序、筛选和分页功能。 还会创建一个简单的分组页面。

    下图显示了完成后页面的外观。 列标题是用户可以单击以按该列排序的链接。 重复单击列标题可在升降排序顺序之间切换。

    Students_Index_page_with_paging

    在本教程中,你将了解:

    • 添加列排序链接
    • 添加“搜索”框
    • 添加分页
    • 创建“关于”页

    一、系统必备

    若要将排序添加到 "学生索引" 页,您将更改 Student 控制器的 Index 方法,并将代码添加到 Student 索引视图。

    1、向 Index 方法添加排序功能

    • ControllersStudentController.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指定排序所依据的列。 此代码在 switch 语句之前创建 IQueryable<T> 变量,在 switch 语句中修改它,并在 switch 语句后调用 ToList 方法。 当创建和修改 IQueryable 变量时,不会向数据库发送任何查询。 在通过调用方法(如 ToList)将 IQueryable 对象转换为集合之前,不会执行查询。 因此,此代码将产生单个查询,该查询在 return View 语句之前不会执行。

    除了为每个排序顺序编写不同 LINQ 语句外,还可以动态创建 LINQ 语句。 有关动态 LINQ 的信息,请参阅DYNAMIC linq

    1. ViewsStudentIndex.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. 运行页面,然后单击 "姓氏" 和 "注册日期" 列标题,验证排序是否正常。

      单击姓氏标题后,学生会按姓氏的降序顺序显示。

    若要将筛选添加到 "学生索引" 页,您将向视图添加一个文本框和一个提交按钮,并在 Index 方法中进行相应的更改。 文本框允许您在 "名字" 和 "姓氏" 字段中输入要搜索的字符串。

    1、向索引方法添加筛选功能

    • ControllersStudentController.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());
      } 

    该代码将 searchString 参数添加到 Index 方法。 从要添加到索引视图的文本框中接收搜索字符串值。 它还将 where 子句添加到 LINQ 语句,该语句仅选择其名字或姓氏中包含搜索字符串的学生。 仅当存在要搜索的值时,才会执行添加 Where 子句的语句。

    Note

    在许多情况下,你可以对实体框架实体集或作为内存中集合上的扩展方法调用同一方法。 结果通常是相同的,但在某些情况下可能会有所不同。

    例如,将空字符串传递给它时,Contains 方法的 .NET Framework 实现将返回所有行,但 SQL Server Compact 4.0 的实体框架提供程序将为空字符串返回零行。 因此,该示例中的代码(将 Where 语句置于 if 语句中)可确保您获得的所有版本 SQL Server 的结果相同。 此外,默认情况下,Contains 方法的 .NET Framework 实现会执行区分大小写的比较,但默认情况下实体框架 SQL Server 提供程序执行不区分大小写的比较。 因此,调用 ToUpper 方法来使测试明确区分大小写确保在稍后更改代码以使用存储库时结果不会发生变化,这将返回一个 IEnumerable 集合,而不是 IQueryable 对象。 (在 Contains 集合上调用 IEnumerable 方法时,将获得 .NET Framework 实现;当在 IQueryable 对象上调用它时,将获得数据库提供程序实现。)

    对于不同的数据库提供程序,或者当你使用 IEnumerable 集合时,与使用 IQueryable 对象相比,Null 处理也可能不同。 例如,在某些情况下,Where 条件(如 table.Column != 0)可能不会返回 null 作为值的列。 默认情况下,EF 会生成附加的 SQL 运算符,使 null 值在数据库中的运行方式与在内存中工作时的工作方式相等,但你可以在 EF6 中设置UseDatabaseNullSemantics标志,或在 EF Core 中调用UseRelationalNulls方法来配置此行为。

    2、向 "学生索引" 视图添加搜索框

    1. ViewsStudentIndex.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 不包含 "a" 搜索字符串,这意味着,如果您将此页面做成书签,则在使用书签时,不会收到筛选后的列表。 这也适用于列排序链接,因为它们将对整个列表进行排序。 在本教程后面的部分中,您将更改 "搜索" 按钮以使用筛选器条件的查询字符串。

    四、添加分页

    若要将分页添加到 "学生索引" 页,你将首先安装PagedList NuGet 包。 然后,在 Index 方法中进行其他更改,并向 Index 视图添加分页链接。 PagedList是 ASP.NET Mvc 的许多良好分页和排序包之一,这里的用途只是一个示例,而不是针对其他选项的建议。

    1、安装 PagedList NuGet 包

    NuGet PagedList包会自动将PagedList包作为依赖项安装。 PagedList包将为 IQueryableIEnumerable 集合安装 PagedList 的集合类型和扩展方法。 扩展方法在 IQueryableIEnumerable之外的 PagedList 集合中创建单个数据页,而 PagedList 集合提供了几个便于分页的属性和方法。 PagedList包安装显示分页按钮的分页帮助程序。

    1. 从 "工具" 菜单中,依次选择 " NuGet 包管理器" 和 "程序包管理器控制台"。

    2. 在 "程序包管理器控制台" 窗口中,确保 "包源" 为 " Nuget.org ",并且 "默认项目" 是 " ContosoUniversity",然后输入以下命令:

      Install-Package PagedList.Mvc 
    3. 生成项目。

    2、向 Index 方法添加分页功能

    1. ControllersStudentController.cs中,添加 PagedList 命名空间的 using 语句:

      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;
      } 

      在方法结束时,学生 IQueryable 对象的 ToPagedList 扩展方法会将学生查询转换为支持分页的集合类型中的一页学生。 然后,将一页学生传递到视图:

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

      ToPagedList 方法需要一个页码。 这两个问号表示null 合并运算符 NULL 合并运算符为可为 NULL 的类型定义默认值;表达式 (page ?? 1) 表示如果 page 有值,则返回该值,如果 page 为 NULL,则返回 1。

    1. ViewsStudentIndex.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 对象。

      PagedList.Mvcusing 语句提供对用于分页按钮的 MVC 帮助器的访问权限。

      代码使用html.beginform的重载,以允许它指定FormMethod

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

      默认html.beginform使用 POST 提交窗体数据,这意味着参数将在 HTTP 消息正文中传递,而不是作为查询字符串在 URL 中传递。 当指定 HTTP GET 时,表单数据作为查询字符串在 URL 中传递,从而使用户能够将 URL 加入书签。 W3C for the 使用 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 

      如果没有要显示的页,则显示 "页 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 大学网站的 "关于" 页,您将显示已注册每个注册日期的学生数。 这需要对组进行分组和简单计算。 若要完成此操作,需要执行以下操作:

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

    1、创建视图模型

    在项目文件夹中创建viewmodel文件夹。 在该文件夹中,添加一个类文件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; }
        }
    } 

    2、修改 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);
      } 

    3、修改“关于”视图

    1. ViewsHomeAbout.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 数据访问-推荐的资源中找到指向其他实体框架资源的链接。

    八、后续步骤

    在本教程中,你将了解:

    • 添加列排序链接
    • 添加“搜索”框
    • 添加分页
    • 创建“关于”页

    转到下一篇文章,了解如何使用连接复原和命令截取。

  • 相关阅读:
    LightOJ 1245(Harmonic Number (II))
    牛客练习赛13 乌龟跑步(DP)
    vue-cli 打包编译 -webkit-box-orient: vertical 被删除解决办法
    vue静态文件处理
    vue项目关闭eslint检查
    Mac 桌面软件开发基础问答
    Mac App开发
    mac os app 开发
    vue中html模板使用绑定的全局函数
    软件版本标识
  • 原文地址:https://www.cnblogs.com/springsnow/p/13262979.html
Copyright © 2020-2023  润新知