基於Asp.Net Core Mvc和EntityFramework Core 的實戰入門教程系列-4


來個目錄吧:
第一章-入門
第二章- Entity Framework Core Nuget包管理
第三章-創建、修改、刪除、查詢
第四章-排序、過濾、分頁、分組
第五章-遷移,EF Core 的codefirst使用
暫時就這么多。后面陸續更新吧

本次教程是完成排序、過濾、分頁和分組功能

在上一個教程中,學生實體的基本CRUD操作實現了一套網頁。在本教程中,您將向學生索引頁面添加排序,過濾和分頁功能。
您還將創建一個簡單分組的頁面。

下圖顯示了完成后頁面的外觀。用戶可以單擊列標題以按該列排序的鏈接。重復單擊列標題可在升序和降序之間切換。

Paste_Image.png

給學生Index頁添加一個排序方法

打開SchoollController.cs 文件,將Index方法,替換為以下代碼:

public async Task<IActionResult> Index(string sortOrder)
{
    ViewData["NameSortParm"] = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
    ViewData["DateSortParm"] = sortOrder == "Date" ? "date_desc" : "Date";
    var students = from s in _context.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(await students.AsNoTracking().ToListAsync());
}

給index方法添加了個sortOrder參數,可以從Url接收值。查詢字符串的值由ASP.NET Core MVC的action方法的參數提供而來。
該參數會是一個字符串,“Name”或者“Date”,可以在后面添加下划線與"desc",指定為降序。默認排序為升序。

第一次加載Index頁面的時候,沒有查詢字符串。學生列表按照升序排序,這個是通過switch的默認值提供的。
當用戶點擊列標題超鏈接的時候,講標題的值賦予sortOrder參數。

ViewData 視圖使用兩個元素(NameSortParm和DateSortParm)來配置列標題超鏈接與對應的查詢字符串值。

public async Task<IActionResult> Index(string sortOrder)
{
    ViewData["NameSortParm"] = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";//手動高亮
    ViewData["DateSortParm"] = sortOrder == "Date" ? "date_desc" : "Date";//手動高亮
    var students = from s in _context.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(await students.AsNoTracking().ToListAsync());
}

String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";//手動高亮
這是一個三元運算符的語句。第一個指定如果sortOrder參數為null或為空,Name的排序應設置為“name_desc”; 否則,應將其設置為空字符串。這兩個語句使視圖可以按如下所示設置列標題超鏈接:

Paste_Image.png

使用LINQ to Entities指定要排序的列。IQueryable在switch語句之前創建一個變量,在switch語句中進行排序,然后執行完畢后ToListAsync。
創建和修改IQueryable變量時,不會向數據庫發送任何查詢。
在IQueryable調用方法將對象轉換為集合之前,不會執行查詢ToListAsync。

將列標題排序添加到Index視圖中

替換Views / Students / Index.cshtml中的代碼,使用以下代碼添加列標題超鏈接。

@model IEnumerable<ContosoUniversity.Models.Student>

@{
    ViewData["Title"] = "Index";
}

<h2>Index</h2>

<p>
    <a asp-action="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <tr>
                <th>
                    <a asp-action="Index" asp-route-sortOrder="@ViewData["NameSortParm"]">@Html.DisplayNameFor(model => model.LastName)</a>
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.FirstMidName)
                </th>
                <th>
                    <a asp-action="Index" asp-route-sortOrder="@ViewData["DateSortParm"]">@Html.DisplayNameFor(model => model.EnrollmentDate)</a>
                </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
@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>
                <a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
                <a asp-action="Details" asp-route-id="@item.ID">Details</a> |
                <a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

此代碼使用屬性中的ViewData信息來使用適當的查詢字符串值設置超鏈接。
運行頁面,然后單擊"LastName"和"EnrollmentDate"列標題,以驗證排序是否有效。

Paste_Image.png

給Student的Index視圖添加搜索功能

要向學生Index頁面添加過濾,您需要向視圖中添加一個文本框和一個提交按鈕,並對Index方法進行相應的更改。文本框將允許您輸入要在名字和姓氏字段中搜索的字符串。

向Index方法添加過濾功能

在StudentsController.cs中,將Index方法替換為以下代碼

public async Task<IActionResult> Index(string sortOrder, string searchString)
{
    ViewData["NameSortParm"] = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
    ViewData["DateSortParm"] = sortOrder == "Date" ? "date_desc" : "Date";
    ViewData["CurrentFilter"] = searchString;//手動高亮

    var students = from s in _context.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(await students.AsNoTracking().ToListAsync());
}

您已向方法添加了searchString參數Index。searchString是從將添加到"Index”視圖的文本框中接收的。
您還在LINQ語句中添加了一個where子句,該代碼表示會搜索Student表中Last Name或者FirstMidName,如果有值則會返回到視圖頁面上。

注意:
這里你調用對象使用Where上的IQueryable方法,在運行的時候在服務器上處理。在某些情況下,您可能將該Where方法調用為內存中集合的擴展方法。
(例如,假設您更改使用_context.Students以便代替EF, DbSet引用返回集合的類型為IEnumerable。)結果通常是相同的,但在某些情況下可能不同。
例如,該Contains方法的.NET Framework實現默認情況下執行,是會區分大小寫的,但在SQL Server中,這是由SQL Server實例的排序規則設置決定的,默認為不區分大小寫
。你可以調用該ToUpper方法讓它顯式不區分大小寫: Where(s => s.LastName.ToUpper().Contains(searchString.ToUpper()).。
這將確保結果保持不變,如果你以后更改代碼使用一個存儲庫,
它返回一個IEnumerable集合而不是一個IQueryable對象(當你調用集合類型為IEnumerable的Contains方法,
你得到的.NET框架實現;當你在一個IQueryable對象上調用它,你得到數據庫提供實現。)
然而,這個解決方案存在性能損失。ToUpper代碼將在TSQL SELECT語句的WHERE子句中放置一個函數。
這將阻止優化程序使用索引。考慮到SQL大多數是安裝為不區分大小寫,最好避免ToUpper代碼,直到遷移到區分大小寫的數據存儲。

在學生Index視圖中添加一個搜索框

在Views/Student/Index.cshtml中,在table標簽之前添加突出顯示的代碼,以創建標題,文本框和搜索按鈕。

<p>
    <a asp-action="Create">Create New</a>
</p>

<form asp-action="Index" method="get">
    <div class="form-actions no-color">
        <p>
            Find by name: <input type="text" name="SearchString" value="@ViewData["currentFilter"]" />
            <input type="submit" value="Search" class="btn btn-default" /> |
            <a asp-action="Index">Back to Full List</a>
        </p>
    </div>
</form>

<table class="table">

此代碼使用

  tagHelper添加搜索文本框和按鈕。
默認情況下,tagHelper使用POST提交表單數據,這意味着參數在HTTP消息正文中傳遞,而不是作為查詢字符串在URL中傳遞。
當您指定使用HTTP GET請求時,表單數據作為查詢字符串傳遞到URL中,這使用戶可以為URL添加書簽。W3C准則建議您在使用GET請求的時候,不會導致數據的修改。

換句話說,就是使用GET請求應該是查詢功能,其他的添加、刪除、修改,不能使用GET請求,建議使用POST請求

運行頁面,輸入任意字符串,然后單擊搜索以驗證搜索是否正常。

Paste_Image.png

請注意,該URL包含搜索字符串(SearchString)。

http://localhost:5813/Students?SearchString=an

在這個階段,如果您點擊列標題排序鏈接,您將丟失在搜索框中輸入的過濾器值。你會在下一節中解決這個問題。

給Student的Index頁面添加分頁功能

要向“學生Index”頁面添加分頁,您將創建一個PaginatedList類,它使用Skip和Take語句來過濾服務器上的數據,
而不是總是檢索表的所有行。然后,您將對方法進行其他更改,Index方法的Index視圖中添加分頁按鈕。下圖顯示了分頁按鈕。

Paste_Image.png

在項目文件夾中創建PaginatedList.cs,然后用下面的代碼替換模板代碼。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;

public class PaginatedList<T> : List<T>
{
    public int PageIndex { get; private set; }
    public int TotalPages { get; private set; }

    public PaginatedList(List<T> items, int count, int pageIndex, int pageSize)
    {
        PageIndex = pageIndex;
        TotalPages = (int)Math.Ceiling(count / (double)pageSize);

        this.AddRange(items);
    }

    public bool HasPreviousPage
    {
        get
        {
            return (PageIndex > 1);
        }
    }

    public bool HasNextPage
    {
        get
        {
            return (PageIndex < TotalPages);
        }
    }

    public static async Task<PaginatedList<T>> CreateAsync(IQueryable<T> source, int pageIndex, int pageSize)
    {
        var count = await source.CountAsync();
        var items = await source.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToListAsync();
        return new PaginatedList<T>(items, count, pageIndex, pageSize);
    }
}

此代碼中的CreateAsync方法采用頁面大小和頁碼,並將適當的Skip和Take語句應用於IQueryable。當IQueryable進行ToListAsync調用時,
它將返回一個只包含請求的頁面的列表。屬性HasPreviousPage,HasNextPage可用於啟用或禁用上一頁和下一頁分頁按鈕。
使用CreateAsync方法,而不是構造函數來創建PaginatedList 對象,因為構造函數不能運行異步代碼。

向Index視圖中添加分頁功能

StudentsController.cs中,將Index方法替換為以下代碼。

public async Task<IActionResult> Index(
    string sortOrder,
    string currentFilter,
    string searchString,
    int? page)
{
    ViewData["CurrentSort"] = sortOrder;
    ViewData["NameSortParm"] = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
    ViewData["DateSortParm"] = sortOrder == "Date" ? "date_desc" : "Date";

    if (searchString != null)
    {
        page = 1;
    }
    else
    {
        searchString = currentFilter;
    }

    ViewData["CurrentFilter"] = searchString;

    var students = from s in _context.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;
    }

    int pageSize = 3;
    return View(await PaginatedList<Student>.CreateAsync(students.AsNoTracking(), page ?? 1, pageSize));
}

下面的代碼將頁碼參數,當前排序順序參數和當前過濾器參數添加到方法中。

public async Task<IActionResult> Index(
    string sortOrder,
    string currentFilter,
    string searchString,
    int? page)

第一次加載頁面時,或者如果用戶沒有單擊分頁或排序鏈接,所有參數將為null。如果單擊分頁鏈接,頁面變量將包含要顯示的頁碼。
名為CurrentSort 的ViewData元素為視圖提供了當前的排序順序,因為這必須包含在分頁鏈接中,以便在分頁時保持排序順序相同。

名為CurrentFilter 的 ViewData元素提供帶有當前過濾器字符串的視圖。此值必須包含在分頁鏈接中,以便在分頁期間過濾器設置,並且必須在重新顯示頁面時將其還原到文本框。

如果在分頁期間更改搜索字符串,則page必須重置為1,因為新的searchString可能導致顯示不同的數據。當在文本框中輸入值並按下提交按鈕時,searchString將更改。在這種情況下,searchString參數不為null。

if (searchString != null)
{
    page = 1;
}
else
{
    searchString = currentFilter;
}

在Index方法結束時,該PaginatedList.CreateAsync方法將學生查詢轉換為支持分頁的List類型中的學生。那個學生集合然后被傳遞到視圖。

return View(await PaginatedList<Student>.CreateAsync(students.AsNoTracking(), page ?? 1, pageSize));

該PaginatedList.CreateAsync方法獲取頁碼。兩個問號表示null合並運算符。空聯合運算符定義可空類型的默認值; 表達式(page ?? 1)意味着返回值,page如果它有一個值,或返回1如果page為null。

向學生索引視圖中添加分頁鏈接

在Views / Students / Index.cshtml中,使用以下代碼替換現有代碼。

@model PaginatedList<ContosoUniversity.Models.Student>

@{
    ViewData["Title"] = "Index";
}

<h2>Index</h2>

<p>
    <a asp-action="Create">Create New</a>
</p>

<form asp-action="Index" method="get">
    <div class="form-actions no-color">
        <p>
            Find by name: <input type="text" name="SearchString" value="@ViewData["currentFilter"]" />
            <input type="submit" value="Search" class="btn btn-default" /> |
            <a asp-action="Index">Back to Full List</a>
        </p>
    </div>
</form>

<table class="table">
    <thead>
        <tr>
            <th>
                <a asp-action="Index" asp-route-sortOrder="@ViewData["NameSortParm"]" asp-route-currentFilter="@ViewData["CurrentFilter"]">Last Name</a>
            </th>
            <th>
                First Name
            </th>
            <th>
                <a asp-action="Index" asp-route-sortOrder="@ViewData["DateSortParm"]" asp-route-currentFilter="@ViewData["CurrentFilter"]">Enrollment Date</a>
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @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>
                    <a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
                    <a asp-action="Details" asp-route-id="@item.ID">Details</a> |
                    <a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
                </td>
            </tr>
        }
    </tbody>
</table>

@{
    var prevDisabled = !Model.HasPreviousPage ? "disabled" : "";
    var nextDisabled = !Model.HasNextPage ? "disabled" : "";
}

<a asp-action="Index"
   asp-route-sortOrder="@ViewData["CurrentSort"]"
   asp-route-page="@(Model.PageIndex - 1)"
   asp-route-currentFilter="@ViewData["CurrentFilter"]"
   class="btn btn-default @prevDisabled btn">
    Previous
</a>
<a asp-action="Index"
   asp-route-sortOrder="@ViewData["CurrentSort"]"
   asp-route-page="@(Model.PageIndex + 1)"
   asp-route-currentFilter="@ViewData["CurrentFilter"]"
   class="btn btn-default @nextDisabled btn">
    Next
</a>

頂部的model類型為PaginatedList 而不是List

列標題鏈接使用查詢字符串將當前搜索字符串傳遞到控制器,以便用戶可以在過濾器結果中排序:

<a asp-action="Index" asp-route-sortOrder="@ViewData["DateSortParm"]" asp-route-currentFilter ="@ViewData["CurrentFilter"]">Enrollment Date</a>

分頁按鈕由tagHelper顯示:

<a asp-action="Index"
   asp-route-sortOrder="@ViewData["CurrentSort"]"
   asp-route-page="@(Model.PageIndex - 1)"
   asp-route-currentFilter="@ViewData["CurrentFilter"]"
   class="btn btn-default @prevDisabled btn">
   Previous
</a>

運行頁面

Paste_Image.png

按不同排序順序單​​擊分頁鏈接,以確保分頁工作正常。然后輸入搜索字符串並再次嘗試分頁,以驗證分頁在排序和過濾時也能正常工作。

創建一個顯示學生統計信息的關於頁面

對於Contoso大學網站的“ 關於”頁面,您將顯示已注冊的學生人數以及注冊日期。這需要對組進行分組和簡單計算。要完成此操作,您需要執行以下操作:

  • 根據業務創建一個ViewModel。
  • 修改Home控制器中的about方法。
  • 修改“About”視圖頁面。

創建視圖模型(ViewModel)

Models文件夾中創建一個SchoolViewModels文件夾。
在新文件夾中,添加類文件EnrollmentDateGroup.cs,並將模板代碼替換為以下代碼:

using System;
using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models.SchoolViewModels
{
    public class EnrollmentDateGroup
    {
        [DataType(DataType.Date)]
        public DateTime? EnrollmentDate { get; set; }

        public int StudentCount { get; set; }
    }
}

修改Home控制器

在HomeController控制器中引入新的 命名空間

using Microsoft.EntityFrameworkCore;
using ContosoUniversity.Data;
using ContosoUniversity.Models.SchoolViewModels;

然后對HomeController中,添加上下文的實例,進行依賴注入。

public class HomeController : Controller
{
    private readonly SchoolContext _context;

    public HomeController(SchoolContext context)
    {
        _context = context;
    }

將About方法的代碼替換為以下代碼:

public async Task<ActionResult> About()
{
    IQueryable<EnrollmentDateGroup> data = 
        from student in _context.Students
        group student by student.EnrollmentDate into dateGroup
        select new EnrollmentDateGroup()
        {
            EnrollmentDate = dateGroup.Key,
            StudentCount = dateGroup.Count()
        };
    return View(await data.AsNoTracking().ToListAsync());
}

使用LINQ語句按照注冊日期將學生實體分組,計算每個組中的實體數,並將結果存儲在EnrollmentDateGroup視圖模型對象的集合中。

注意
在1.0版本的Entity Framework Core中,將整個結果集返回給客戶端,並在客戶端上進行分組。在某些情況下,這可能會導致性能問題。
確保使用生產數據量測試性能,並且如果需要,可以使用原始SQL在服務器上進行分組。有關如何使用原始SQL的信息,請參閱

修改關於視圖

使用以下代碼替換Views / Home / About.cshtml文件中的代碼:

@model IEnumerable<ContosoUniversity.Models.SchoolViewModels.EnrollmentDateGroup>

@{
    ViewData["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>

運行應用程序,然后單擊關於鏈接。每個注冊日期的學生數量顯示在表中。

Paste_Image.png


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM