建立ASP.NET Core MVC應用程序(5)-添加查詢功能 & 新字段

建立ASP.NET Core MVC應用程序(5)-添加查詢功能 & 新字段

添加查詢功能

本文將實現經過Name查詢用戶信息。html

首先更新GetAll方法以啓用查詢:數據庫

public async Task<IEnumerable<User>> GetAll(string searchString)
{
    var users = from u in _context.Users
                select u;

    if (!string.IsNullOrEmpty(searchString))
    {
        users = users.Where(u => u.Name.Contains(searchString));
    }

    return await users.ToListAsync();
}

第一行的LINQ查詢僅僅在這裏做了定義,並無在這裏實際操做數據庫。bash

LINQ查詢在被定義或者經過調用相似於WhereContainsOrderBy的方法進行修改的時候不會執行。相反的,查詢會延遲執行,好比在ToListAsync方法被調用以後。服務器

Contains方法會在數據庫中運行,而不是上面的C#代碼,在數據庫中,Contains會被映射成不區分大小寫的SQLLIKE數據結構

因爲u => u.Name.Contains(searchString)Lambda表達式在當前我所使用的MySQL Connector/NET版本中運行報錯(新版本好像已經修復了該問題,這裏懶得更新了),因此這裏先改爲:mvc

users = users.Where(u => u.Name == searchString);

導航到http://localhost:5000/User,添加?searchString=Zhu查詢字符串,將過濾出指定的用戶信息(http://localhost:5000/User?searchString=Zhu)。app

若是你修改Index方法的簽名使得方法包含一個名爲id的參數,那麼id參數將會匹配Startup.cs文件中設置的默認路由的可選項{id?}框架

app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "default",
        template: "{controller=Home}/{action=Index}/{id?}");
});

Rename後的方法:asp.net

public async Task<IActionResult> Index(string id)
{
    var users = from u in _context.Users
                select u;
    if (!String.IsNullOrEmpty(id))
    {
        users = users.Where(u => u.Name == id);
    }

    return View(await users.ToListAsync());
}

如今你能夠傳遞這個Name查詢條件做爲路由數據(URL Segment)來代替查詢字符串(http://localhost:5000/User/Index/Zhu)。async

然而,咱們不能期望用戶每次都經過修改URL來進行查詢,咱們經過添加UI來進行查詢。若是你想改變Index方法的簽名來測試怎樣傳遞路由綁定ID參數,將searchString參數改回來。

接着,打開Views/User/Index.cshtml文件,添加<form>標記:

<form asp-controller="User" asp-action="Index">
    <p>
        姓名: <input type="text" name="SearchString">
        <input type="submit" value="過濾" />
    </p>
</form>

這裏HTML<form>標籤使用了Form Tag Helper,因此當你提交這個表單時,過濾字符串會被傳遞到User控制器的Index方法中。

這裏沒有使用你所但願的[HttpPost] Index重載方法,其實根本不須要該方法,由於這個方法並無改變這個應用的狀態,僅僅用來過濾數據。

你能夠添加下面的[HttpPost] Index方法。

[HttpPost]
public string Index(string searchString, bool notUsed)
{
    return "From [HttpPost]Index: filter on " + searchString;
}

notUsed參數用於建立一個重載的Index方法。若是你添加了該方法,動做方法將會調用匹配的[HttpPost] Index方法。

這個時候點擊過濾按鈕時,會顯示以下界面:

然而,儘管你添加了[HttpPost]版本的Index方法,最終仍然存在侷限性。想象你將一個指定的查詢做爲書籤或者將查詢結果做爲一個連接發送給你的朋友以便他們在打開時能看到一樣的過濾結果時,注意HTTP POST請求的URL和GET請求的URL(http://localhost:5000/User)是同樣的-在URL裏面沒有任何查詢信息。查詢信息是做爲表單數據發送到服務器的。

在請求體中能夠看到查詢參數和XSRF反僞造標記(經過Form Tag Helper生成),因爲查詢沒有修改數據,因此無需在控制器方法中驗證該標記。

爲了把查詢參數從請求體中移到URL中,必須把請求指定爲HTTP GET

<form asp-controller="Movies" asp-action="Index" method="get">

此時當你再提交時,URL將會包含具體的查詢條件。查詢將會跳轉到HttpGet Index方法,即便存在着HttpPost Index方法。

添加新的字段

咱們將經過Entity Framework Code First Migrations工具來添加一個新的字段到模型中,並將新的改變同步到數據庫中。

當你使用EF Code First自動建立一個數據庫時,Code First會添加一個表到數據庫中幫助跟蹤數據庫的數據結構是否和模型類保持同步。若是不一樣步,EF會拋出一個異常。

添加身高屬性到模型類

public class User
{
    public int ID { get; set; }
    [Display(Name = "姓名")]
    public string Name { get; set; }
    [Display(Name = "郵箱")]
    [DataType(DataType.EmailAddress)]
    public string Email { get; set; }
    [Display(Name = "簡介")]
    public string Bio { get; set; }

    // 新添加的屬性
    [Display(Name = "身高")]
    public decimal Height { get; set; }

    [Display(Name = "職稱")]
    public string Title { get; set; }

    [Display(Name = "部門")]
    public string Dept { get; set; }
}

Build該應用(dotnet build)。可能因爲我所使用的MySQL EF Core provider還不成熟,添加DateTime類型的字段時會報錯(新版本可能會修復該問題,這裏懶得去測試了)。

因爲你添加了一個新的字段到User類,因此你須要更新所綁定的白名單,這樣新的屬性將會包含這內。爲CreateEdit方法更新[Bind]特性以包含Height屬性。

[Bind("ID,Name,Email,Bio,Height,Title,Dept")]

爲了顯示新的字段還要更新視圖模板。

打開Index.cshtmlCreate.cshtmlEdit.cshtml等文件,添加新的Height字段。

而後須要更新數據庫以包含新的字段。若是沒有更新,此時運行將報錯,由於更新後User模型類和已存在的數據庫User表的結構不一致。

有如下幾種方案來解決該錯誤:

  • EF能夠基於新的模型類自動刪除並重建數據庫。開發階段在測試數據庫上作開發仍是比較方便的,可是你會丟失數據庫中的現有數據。所以該方案不適用於生產環境數據庫!

  • 顯式修改現有數據庫的結構,使得它與模型類相匹配,這個方案可讓你保留數據庫的現有數據。你能夠經過手動或者數據庫腳原本進行變動。

  • 使用Code First Migrations來更新數據庫結構。

這裏採用第三種方案,運行以下命令:

dotnet ef migrations add Height
dotnet ef database update

migrations add命令告訴Migration框架去檢查當前的User模型類和當前的User數據庫表結構是否一致。若是不一致,就建立必要的代碼來遷移數據庫到新的模型類。

基於新添加的「部門」字段進行查詢

Models目錄下添加UserDeptViewModel類:

using Microsoft.AspNetCore.Mvc.Rendering;

public class UserDeptViewModel
{
    public List<User> users;
    public SelectList depts;
    public string userDept { get; set; }
}

這個視圖模型(View Model)將包含:

  • 用戶列表users
  • 包含部門列表(depts)的SelectList,將用於視圖頁面中容許用戶去從列表中選擇一個部門。
  • userDept,包含用戶所選中的部門(dept)。

修改Index方法:

public async Task<IActionResult> Index(string userDept, string searchString)
{
    IQueryable<string> deptQuery = from u in _context.Users
                                    orderby u.Dept
                                    select u.Dept;

    var users = from u in _context.Users
                select u;
    if (!String.IsNullOrEmpty(searchString))
    {
        users = users.Where(u => u.Name == searchString);
    }

    if (!String.IsNullOrEmpty(userDept))
    {
        users = users.Where(u => u.Dept == userDept);
    }

    var userDeptVM = new UserDeptViewModel();
    userDeptVM.depts = new SelectList(await deptQuery.Distinct().ToListAsync());
    userDeptVM.users = await users.ToListAsync();

    return View(userDeptVM);
}

下面的代碼經過LINQ查詢從數據庫獲取全部的部門數據。

IQueryable<string> deptQuery = from u in _context.Users
                                    orderby u.Dept
                                    select u.Dept;

depts的SelectList是經過投影不重複的部門(Distinct)建立的。

userDeptVM.depts = new SelectList(await deptQuery.Distinct().ToListAsync());

在Index視圖中添加「部門」查詢字段

首先刪除最頂部的@model:

@model IEnumerable<MyFirstApp.Models.User>

替換成:

@model UserDeptViewModel

<form>標記中添加:

<select asp-for="userDept" asp-items="Model.depts">
    <option value="">全部</option>
</select>

<table>標記中分別刪除:

@Html.DisplayNameFor(model => model.Dept)

@foreach (var item in Model) {

替換成:

@Html.DisplayNameFor(model => model.users[0].Dept)
@foreach (var item in Model.users) {

最終的Index.cshtml文件以下:

@model UserDeptViewModel

@{
    ViewData["Title"] = "Index - User List";
}

<h2>首頁 - 用戶列表</h2>

<p>
    <a asp-action="Create">新建</a>
</p>

<form asp-controller="User" asp-action="Index" method="GET">
    <p>

        <select asp-for="userDept" asp-items="Model.depts">
            <option value="">全部</option>
        </select>

        姓名: <input type="text" name="SearchString">
        <input type="submit" value="過濾" />
    </p>
</form>

<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.users[0].Name)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.users[0].Email)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.users[0].Height)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.users[0].Title)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.users[0].Dept)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.users[0].Bio)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
@foreach (var item in Model.users) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Name)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Email)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Height)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Title)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Dept)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Bio)
            </td>
            <td>
                <a asp-action="Edit" asp-route-id="@item.ID">編輯</a> |
                <a asp-action="Details" asp-route-id="@item.ID">詳情</a> |
                <a asp-action="Delete" asp-route-id="@item.ID">刪除</a>
            </td>
        </tr>
}
    </tbody>
</table>

最終運行的頁面:

我的博客

個人我的博客

相關文章
相關標籤/搜索