建立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>

最终运行的页面:

我的博客

个人我的博客

相关文章
相关标签/搜索