創(chuàng)建ASP.NET Core MVC應用程序(5)-添加查詢功能 & 新字段
添加查詢功能
本文將實現(xiàn)通過Name查詢用戶信息。
首先更新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查詢僅僅在這里作了定義,并沒有在這里實際操作數(shù)據(jù)庫。
LINQ查詢在被定義或者通過調(diào)用類似于Where、Contains、OrderBy的方法進行修改的時候不會執(zhí)行。相反的,查詢會延遲執(zhí)行,比如在ToListAsync方法被調(diào)用之后。
Contains方法會在數(shù)據(jù)庫中運行,而不是上面的C#代碼,在數(shù)據(jù)庫中,Contains會被映射成不區(qū)分大小寫的SQLLIKE。
由于u => u.Name.Contains(searchString)Lambda表達式在當前我所使用的MySQL Connector/NET版本中運行報錯(新版本好像已經(jīng)修復了該問題,這里懶得更新了),所以這里先改成:
users = users.Where(u => u.Name == searchString);
導航到http://localhost:5000/User,添加?searchString=Zhu查詢字符串,將過濾出指定的用戶信息(http://localhost:5000/User?searchString=Zhu)。
如果你修改Index方法的簽名使得方法包含一個名為id的參數(shù),那么id參數(shù)將會匹配Startup.cs文件中設(shè)置的默認路由的可選項{id?}。
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
Rename后的方法:
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());
}
現(xiàn)在你可以傳遞這個Name查詢條件作為路由數(shù)據(jù)(URL Segment)來代替查詢字符串(http://localhost:5000/User/Index/Zhu)。
然而,我們不能指望用戶每次都通過修改URL來進行查詢,我們通過添加UI來進行查詢。如果你想改變Index方法的簽名來測試怎樣傳遞路由綁定ID參數(shù),將searchString參數(shù)改回來。
接著,打開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重載方法,其實根本不需要該方法,因為這個方法并沒有改變這個應用的狀態(tài),僅僅用來過濾數(shù)據(jù)。
你可以添加下面的[HttpPost] Index方法。
[HttpPost]
public string Index(string searchString, bool notUsed)
{
return "From [HttpPost]Index: filter on " + searchString;
}
notUsed參數(shù)用于創(chuàng)建一個重載的Index方法。如果你添加了該方法,動作方法將會調(diào)用匹配的[HttpPost] Index方法。
這個時候點擊過濾按鈕時,會顯示如下界面:

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

在請求體中可以看到查詢參數(shù)和XSRF反偽造標記(通過Form Tag Helper生成),由于查詢沒有修改數(shù)據(jù),所以無需在控制器方法中驗證該標記。
為了把查詢參數(shù)從請求體中移到URL中,必須把請求指定為HTTP GET。

<form asp-controller="Movies" asp-action="Index" method="get">
此時當你再提交時,URL將會包含具體的查詢條件。查詢將會跳轉(zhuǎn)到HttpGet Index方法,即使存在著HttpPost Index方法。
添加新的字段
我們將通過Entity Framework Code First Migrations工具來添加一個新的字段到模型中,并將新的改變同步到數(shù)據(jù)庫中。
當你使用EF Code First自動創(chuàng)建一個數(shù)據(jù)庫時,Code First會添加一個表到數(shù)據(jù)庫中幫助跟蹤數(shù)據(jù)庫的數(shù)據(jù)結(jié)構(gòu)是否和模型類保持同步。如果不同步,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類,所以你需要更新所綁定的白名單,這樣新的屬性將會包含這內(nèi)。為Create和Edit方法更新[Bind]特性以包含Height屬性。
[Bind("ID,Name,Email,Bio,Height,Title,Dept")]
為了顯示新的字段還要更新視圖模板。
打開Index.cshtml、Create.cshtml、Edit.cshtml等文件,添加新的Height字段。
然后需要更新數(shù)據(jù)庫以包含新的字段。如果沒有更新,此時運行將報錯,因為更新后User模型類和已存在的數(shù)據(jù)庫User表的結(jié)構(gòu)不一致。
有以下幾種方案來解決該錯誤:
EF可以基于新的模型類自動刪除并重建數(shù)據(jù)庫。開發(fā)階段在測試數(shù)據(jù)庫上做開發(fā)還是比較方便的,但是你會丟失數(shù)據(jù)庫中的現(xiàn)有數(shù)據(jù)。因此該方案不適用于生產(chǎn)環(huán)境數(shù)據(jù)庫!
顯式修改現(xiàn)有數(shù)據(jù)庫的結(jié)構(gòu),使得它與模型類相匹配,這個方案可以讓你保留數(shù)據(jù)庫的現(xiàn)有數(shù)據(jù)。你可以通過手動或者數(shù)據(jù)庫腳本來進行變更。
使用Code First Migrations來更新數(shù)據(jù)庫結(jié)構(gòu)。
這里采用第三種方案,運行如下命令:
dotnet ef migrations add Height
dotnet ef database update
migrations add命令告訴Migration框架去檢查當前的User模型類和當前的User數(shù)據(jù)庫表結(jié)構(gòu)是否一致。如果不一致,就創(chuàng)建必要的代碼來遷移數(shù)據(jù)庫到新的模型類。
基于新添加的“部門”字段進行查詢
在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查詢從數(shù)據(jù)庫獲取所有的部門數(shù)據(jù)。
IQueryable<string> deptQuery = from u in _context.Users
orderby u.Dept
select u.Dept;
depts的SelectList是通過投影不重復的部門(Distinct)創(chuàng)建的。
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>
最終運行的頁面:
