来个目录吧:

第一章-入门

第二章- Entity Framework Core Nuget包管理

第三章-创建、修改、删除、查询

第四章-排序、过滤、分页、分组

第五章-迁移,EF Core 的codefirst使用

暂时就这么多。后面陆续更新吧

创建、查询、更新、删除

这章主要讲解使用EF完成 增删改查的功能。

自定义“详情信息”页面

我们通过基架生成的代码,没有包含“Enrollments”的属性,该导航属性是一个集合,所以我们在详情信息页面,需要将他们显示到html表格中。

在Controllers / StudentsController.cs中,详细信息视图的操作方法使用该SingleOrDefaultAsync方法查询单个Student实体。添加Include、ThenInclude,和AsNoTracking方法,如下面突出显示的代码所示。

public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
} var student = await _context.Students
.Include(s => s.Enrollments)
.ThenInclude(e => e.Course)
.AsNoTracking()
.SingleOrDefaultAsync(m => m.ID == id); if (student == null)
{
return NotFound();
} return View(student);
}

Include 和 ThenInclude 两个方法会让Context去额外加载Student的导航属性Enrollments,和Enrollments的导航属性Course。

而AsNoTracking方法在其中返回的实体信息,不存在在DbContext的生命周期中,他可以提高我们的查询性能。AsNoTracking 在后面会额外提及。

路由数据

传递到Details方法中的参数信息,是通过路由控制的。路由是数据从模型绑定中获取到的URL。例如,默认路由指定Controller、Action和id来组成。

    app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");//手动高亮
}); DbInitializer.Initialize(context);
}

在下面的URL中,路由将由Instructor作为控制器,Index作为操作,1作为指定id;

http://localhost:1230/Instructor/Index/1?courseID=2021

URL的最后一部分(“?courseID = 2021”)是一个查询字符串值。如果将其作为查询字符串值传递,则模型绑定器还会将ID值传递给Details方法id参数:

http://localhost:1230/Instructor/Index/1?courseID=2021

在Index页面中,超链接是由Razor视图中的标记语句创建的,在下面的Razor代码中,id参数作为默认路由相匹配,因此id会添加到“asp-route-id”中。

<a asp-action="Edit" asp-route-id="@item.ID">Edit</a>

在以下的代码中,studentID与默认的路由参数不匹配,因此将会被作为添加查询操作。

<a asp-action="Edit" asp-route-studentID="@item.ID">Edit</a>

将enrollments 添加到“详情信息”页面中

打开“ Views/Students/Details.cshtml” 使用DisplayNameForDisplayFor显示每个字段,如以下示例所示:

<dt>
@Html.DisplayNameFor(model => model.LastName)
</dt>
<dd>
@Html.DisplayFor(model => model.LastName)
</dd>

需要你在Details.cshtml中

在最后一个

标记之前,添加以下代码以显示登记列表:

<dt>
@Html.DisplayNameFor(model => model.Enrollments)
</dt>
<dd>
<table class="table">
<tr>
<th>Course Title</th>
<th>Grade</th>
</tr>
@foreach (var item in Model.Enrollments)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Course.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.Grade)
</td>
</tr>
}
</table>
</dd>

以上代码会循环Enrollments导航属性中的所有实体信息。显示出每个学生登记了的课程名称、成绩信息。课程标题是通过Enrollments的导航属性Course显示出来。

运行程序, 选择student 菜单,然后再选择“Details”按钮,可以看到如下信息

修改创建页面

SchoolController中,修改标记了HttpPost特性的Create方法,添加一个try-catch块,并且从Bind特性中将“ID”参数删除掉。

  [HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(
[Bind("EnrollmentDate,FirstMidName,LastName")] Student student)
{
try
{
if (ModelState.IsValid)
{
_context.Add(student);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
}
catch (DbUpdateException /* ex */)
{
//错误日志(可以在这里记录错误的变量名称,把他写到日志文件中)
//Log the error (uncomment ex variable name and write a log.
ModelState.AddModelError("", $"信息无法保存更改,请再试一次, 如果问题依然存在。可以联系你的系统管理员 - 角落的白板笔");
}
return View(student);
}
  • 以上代码是指 由ASP.NET MVC的模型,绑定创建的一个Student实体添加到Students实体集合中,然后将发生的更改保存到数据库中。

  • 而需要将ID从Bind特性中删除,是因为ID为主键值,SQL Server将在插入行时自动递增该值。不需要用户进行ID设置。

  • 除了Bind特性之外,添加的try-catch块是对代码做的额外的变动,如果DbUpdateException在保存更改时捕获到异常,则会显示一个通用错误消息。DbUpdateException异常有时是由程序外部的某些东西引起的,而不是程序本身错误,因此建议用户重试。

  • ValidateAntiForgeryToken 属性有助于防止跨站点请求伪造(CSRF)攻击。

关于 overposting(过多发布)的安全注意

通过基架生成的代码Create方法中包含了Bind特性是为了防止发生overposting的一种情况。

  • 举个栗子:假如学生实体包含 了Secret字段,但是你不希望从网页来设置它的信息。
public class Student
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public string Secret { get; set; }
}

overposting发生的情况就是,即使你的网页上没有Secret字段,但是黑客可以通过某些工具(如:findder)或者用JavaScript点,发布一个form表单请求。里面包含了Secret字段。

如果你没有Bind特性的话,就会创建一个含有Secret的Student实体信息,然后黑客伪造的值就会更新到数据库中。

下图,展示了使用Fiddler工具,给Secret字段赋值,发送请求到数据库中。(值为:“OverPost”)

尽管你没有从网页上显示Secret字段,但是黑客通过工具,强行将值赋予了“Secret”。

使用带有Include的Bind特性来把参数列入白名单是一种最佳的方法。当然也可以使用Exclude参数来将字段排除除去作为黑名单,也可以实现。但是使用Exclude的问题是如果添加了新字段默认会被排除,不会被保护。所以最佳的做法还是使用Include的做法。

本教程中,使用了在编辑的时候先从数据库中查询实体,然后再调用TryUpdateModel方法,然后传递允许的属性列表,来防止overposting。

另一种防止overposting的方法是许多开发人员所接受的,它使用视图模型而不是直接使用实体类。 仅在视图模型中包含要更新的属性。 一旦MVC模型绑定完成,将视图模型属性复制到实体实例,可选地使用AutoMapper等工具。 使用实体实例上的_context.Entry将其状态设置为Unchanged,然后在视图模型中包含的每个实体属性上设置Property(“PropertyName”)IsModified为true。 此方法适用于编辑和创建场景。

作为优秀的程序员,尽量使用DTO,也就是上面说的viewmodel(视图模型),而不是使用实体。DTO的优点以后我们有机会再说。

修改创建视图页面

在路径“/Views/Students/Create.cshtml”,使用label,input,span标签(目的是为了做验证)帮助完善每个字段。

通过选择“Students”选项卡,点击“Create”运行该页面。

输入无效的时间,然后点击Create以查看错误消息。

这个是默认通过服务器端验证,报错的信息。在后面的教程中,会讲解如果添加客户端的验证信息。

  [HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(
[Bind("EnrollmentDate,FirstMidName,LastName")] Student student)
{
try
{
if (ModelState.IsValid) //手动高亮,这里就是在做字段验证信息
{
_context.Add(student);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
}
catch (DbUpdateException /* ex */)
{
//错误日志(可以在这里记录错误的变量名称,把他写到日志文件中)
//Log the error (uncomment ex variable name and write a log.
ModelState.AddModelError("", $"信息无法保存更改,请再试一次, 如果问题依然存在。可以联系你的系统管理员 - 角落的白板笔");
}
return View(student);
}

只需要将日期修改为正确的值,然后点击Create就可以添加信息成功。

修改编辑功能

SchoolController.cs文件中,HttpGet 特性的Edit方法(没有HttpPost属性的SingleOrDefaultAsync方法)该方法是搜索所选的学生实体,就像您在Details方法中看到的一样。您不需要更改此方法。

我们需要替换的是标记了HttpPost特性 的Edit方法代码为以下代码。

 [HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditPost(int? id)
{
if (id == null)
{
return NotFound();
}
var studentToUpdate = await _context.Students.SingleOrDefaultAsync(s => s.ID == id);
if (await TryUpdateModelAsync<Student>(
studentToUpdate,
"",
s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
{
try
{
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
catch (DbUpdateException /* ex */)
{
//错误日志(可以在这里记录错误的变量名称,把他写到日志文件中)
ModelState.AddModelError("", $"信息无法保存更改,请再试一次, 如果问题依然存在。可以联系你的系统管理员 - 角落的白板笔"); }
}
return View(studentToUpdate);
}
  • 上面的修改内容,我们一个个慢慢的说,目的就是为了防止overposting,采用了bind包含白名单的方法来进行参数传递。这是一种最佳的安全做法。

  • 新的代码会读取现有的实体,并执行TryUpdateModel方法,这里是mvccore的框架使用了taghelper语法,将页面上的Student实体信息做了更新。然后

    EF框架会自动更改实体状态为Modifed。然后当我们执行SaveChange的时候,EF会创建sql语句来更新数据到数据库中。(这里没有考虑并发冲突,我们后面再来解决这个问题)

  • 作为防止overposting的最佳做法,你在“Edit”视图页面中,显示的字段已经更新到了TryUpdateModel的白名单中了。

替代原HttpPost Edit方法

推荐的方法可以保证,我们只修改了可以保证业务需要的字段,但是可能会引发并发冲突。他也增加了一次数据库额外的查询开销。

以下是替代方法,但是我们当前项目不要使用以下代码。这里只是作为一个说明。

public async Task<IActionResult> Edit(int id, [Bind("ID,EnrollmentDate,FirstMidName,LastName")] Student student)
{
if (id != student.ID)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(student);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists, " +
"see your system administrator.");
}
}
return View(student);
}

上面的方法是网页需要更新所有字段的时候,可以上面的方法,否则建议不考虑。

实体状态

数据库上下文跟踪内存中的实体是否和数据库的一致,并由此来确定在调用SaveChanges方法的时候进行何种操作。例如:当新的 实体传递给add方法的时候,该实体的状态将被设置为Added。然后调用SaveChange方法的时候,数据库上下文会发Sql inser命令。

实体状态可能有以下的状态:

  • Added。实体尚不在数据库中,执行SaveChange方法的时候发出Insert语句。

  • **Unchanged*。执行SaveChange方法的时候,不会对此实体进行任何操作。当你

    从数据库查询某个实体的时候,实体的状态就是从它开始的。

  • Modified。 实体的部分或者全部属性被修改的时候。调用SaveChange方法会发出Update 语句。

  • Deleted。表示实体已经被标记为删除状态。调用SaveChange方法会发出Delete语句。

  • Detached。该实体没有被数据库上下文跟踪。

在桌面程序中(C/S),状态更改通常会自动设置。您读取实体并更改某些字段的时候。这将导致其实体状态自动更改为Modified。然后调用SaveChanges时,Entity Framework生成一个SQL UPDATE语句,修改你实体的更改字段值。

在webapp开发中。DbContext读取实体并显示其要编辑的数据库展现在页面上,当发送Post请求到Edit方法的时候,会创建一个新的web请求,并创建一个新的DbContext,如果你在新上下文中重新获取实体,整个请求过程类似桌面处理。

但是如果你不想做额外的查询操作,你必须使用由model-binder创建的实体对象。最简单的方法是将实体状态设置为modifed,就像之前显示的HttpPost编辑代码中所做的那样。然后当调用SaveChanges时,Entity Framework会更新数据库行的所有字段信息,因为数据库上下文无法知道您更改了哪些属性。

如果想避免read-first方法,但是希望使用SQLUupdate语句来更新用户实际想更改的字段,代码会更加的复杂。你必须以某种方式保存原始值(例如,通过隐藏字段),以便调用post请求的edit方法的时候可以用。然后,可以使用原始值创建一个Student实体信息。调用Attach该实体的原始方法,将实体的值更新为新值,最后调用SaveChange。

测试编辑页面

运行应用程序并选择“Student”选项卡,点击“编辑”超链接。

更改一些数据,然后点击保存按钮。返回Index视图页面,可以看到更改的数据。

修改删除页面

StudentController.cs文件中,HttpGet请求的Delete方法中使用了

SingleOrDefaultAsync

来查询实体,与“Detail”和“Editor”视图页面一样。但是为了调用SaveChange失败的时候实现一些自定义错误信息,我们需要向此方法和视图添加一些代码。

删除功能与编辑和创建功能一样,需要操作两个方法。相应Get请求去调用方法显示一个视图,该视图为用户提供一个删除或者取消的操作按钮。

如果用户同意的话,则会创建一个POST请求。然后就会调用Post的Delete方法,然后执行方法删除掉他。

我们将会对HttpPost特性下 的Delete方法添加一个try-catch块,以便显示处理数据库修改的时候发生的错误。

修改HttpPost特性的Delete代码如下:

···

    // GET: Students/Delete/5
public async Task<IActionResult> Delete(int? id, bool? saveChangesError = false)
{
if (id == null)
{
return NotFound();
} var student = await _context.Students
.AsNoTracking()
.SingleOrDefaultAsync(m => m.ID == id);
if (student == null)
{
return NotFound();
} if (saveChangesError.GetValueOrDefault())
{
ViewData["ErrorMessage"] =
$"删除{student.LastName}信息失败,请再试一次, 如果问题依然存在。可以联系你的系统管理员 - 角落的白板笔";
} return View(student);
}

···

此代码增加了一个可选参数,该参数指示在保存更改失败后是否调用该方法。当在Delete没有失败的情况下,调用HttpGet 方法时,此参数为false 。当HttpPost的 Delete方法执行数据库更新错误而调用它时,参数为true,并且错误消息传递到视图。

HttpPost的read-first的删除方法

我们修改DeleteConfirmed方法的代码,如下:

[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
var student = await _context.Students
.AsNoTracking()
.SingleOrDefaultAsync(m => m.ID == id);
if (student == null)
{
return RedirectToAction("Index");
} try
{
_context.Students.Remove(student);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
return RedirectToAction("Delete", new { id = id, saveChangesError = true });
}
}

此代码先搜索选定的实体,然后调用Remove将实体的状态修改为Deleted。当SaveChanges调用时,将生成SQL DELETE命令。

另外的一种写法

如果程序需要提高性能作为优先级考虑,可以参考一下的代码。他是仅仅通过Id主键

实例化Student实体,然后通过更改实体的状态值来避免sql查询,然后来删除实体信息(

这段代码不要放到项目中去,只作为参考。)

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
try
{
Student studentToDelete = new Student() { ID = id };
_context.Entry(studentToDelete).State = EntityState.Deleted;
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
return RedirectToAction("Delete", new { id = id, saveChangesError = true });
}
}

如果实体具有应删除的相关数据,请确保在数据库中配置开启级联删除。上面通过这种实体删除的方法,EF可能不会删除的相关实体。

修改“删除”视图

在Views / Student / Delete.cshtml中,在h2标题和h3标题之间添加一条错误消息,如以下示例所示:

<h2>Delete</h2>
<p class="text-danger">@ViewData["ErrorMessage"]</p>
<h3>Are you sure you want to delete this?</h3>

单击“ 删除”。将显示“Index”页面,但没有删除的学生。(您将在并发教程中看到一个错误处理代码的示例。)

关闭数据库连接

要释放数据库连接所拥有的资源,必须在完成上下文实例后尽快处理该上下文实例。

ASP.NET Core内置依赖注入为您完成此任务。

Startup.cs中,您调用AddDbContext扩展方法以DbContext在ASP.NET DI容器中配置类。默认服务生命周期设置为Scoped意味着上下文对象生存期与Web请求生命周期一致,并且该Dispose方法将在Web请求结束时自动调用。

事务处理

默认情况下,Entity Framework默认实现事务。

在您对多个行或表进行更改然后调用的情况下SaveChanges,Entity Framework会自动确保所有更改都成功或全部失败。

如果先执行某些更改,然后发生错误,那么这些更改会自动回滚。

对于需要更多控制的方案 - 例如,如果要在事务中包括在Entity Framework之外完成的操作 - 请参阅事务

无跟踪查询 AsNoTracking

这里我就不翻译了,自己摘录了博客园的实例

性能提升之AsNoTracking



我们看生成的sql



sql是生成的一模一样,但是执行时间却是4.8倍。原因仅仅只是第一条EF语句多加了一个AsNoTracking。

注意:

AsNoTracking干什么的呢?无跟踪查询而已,也就是说查询出来的对象不能直接做修改。所以,我们在做数据集合查询显示,而又不需要对集合修改并更新到数据库的时候,一定不要忘记加上AsNoTracking。

如果查询过程做了select映射就不需要加AsNoTracking。如:db.Students.Where(t=>t.Name.Contains("张三")).select(t=>new (t.Name,t.Age)).ToList();

基于Asp.Net Core Mvc和EntityFramework Core 的实战入门教程系列-3的更多相关文章

  1. 基于Asp.Net Core Mvc和EntityFramework Core 的实战入门教程系列-1

    来个目录吧: 第一章 第二章 第三章 暂时就这么多.后面路线更新吧 本系列文章为翻译加上我个人的使用心得理解,希望帮助热爱学习的程序员. 珍重声明:本系列文章会跟原文有点出入,去掉了罗里吧嗦的文字. ...

  2. 基于Asp.Net Core Mvc和EntityFramework Core 的实战入门教程系列-4

    来个目录吧: 第一章-入门 第二章- Entity Framework Core Nuget包管理 第三章-创建.修改.删除.查询 第四章-排序.过滤.分页.分组 第五章-迁移,EF Core 的co ...

  3. 基于Asp.Net Core Mvc和EntityFramework Core 的实战入门教程系列-2

    来个目录吧: 第一章 第二章 第三章 暂时就这么多.后面路线更新吧 Entity Framework Core Nuget包管理 如果你创建项目的时候启用了个人身份验证的话,项目中就已经包含了EFCo ...

  4. 基于Asp.Net Core Mvc和EntityFramework Core 的实战入门教程系列-5

    来个目录吧: 第一章-入门 第二章- Entity Framework Core Nuget包管理 第三章-创建.修改.删除.查询 第四章-排序.过滤.分页.分组 第五章-迁移,EF Core 的co ...

  5. 基于asp.net(C#)MVC+前端bootstrap+ztree+lodash+jquery技术-Angel工作室通用权限管理

    一.Angel工作室简单通用权限系统简介 AngelRM(Asp.net MVC Web api)是基于asp.net(C#)MVC+前端bootstrap+ztree+lodash+jquery技术 ...

  6. 基于ASP.NET的MVC框架下的MvcPaper分页控件的使用技术

    using System; using System.Collections.Generic; using System.Linq; using System.Web; using Webdiyer. ...

  7. 基于Asp.Net Core 2.1的简单问答社区系统源代码分享

    看见园子里很多人都在分享源代码,我也来凑个热闹. 该项目基于.NET CORE 2.1(其实是从1.1开始开发的),经历过不停的调整终于有个能拿出手的版本了,第一次在博客园发文章. 使用到的技术以及框 ...

  8. ASP.NET Core 入门教程 6、ASP.NET Core MVC 视图布局入门

    一.前言 1.本教程主要内容 ASP.NET Core MVC (Razor)视图母版页教程 ASP.NET Core MVC (Razor)带有Section的视图母版页教程 ASP.NET Cor ...

  9. ASP.NET Core 入门笔记7,ASP.NET Core MVC 视图布局入门

    一.前言 1.本教程主要内容 ASP.NET Core MVC (Razor)视图母版页教程 ASP.NET Core MVC (Razor)带有Section的视图母版页教程 ASP.NET Cor ...

随机推荐

  1. Effective java -- 2 对于所有对象都通用到方法

    第八条:覆盖equals时请遵守通用约定 什么时候需要覆盖equals方法?类具有自己的逻辑相等概念,并且父类的equals方法不能满足需要.重写equals时需要遵循一下约定: 自反性:非null ...

  2. Linux文件权限与目录配置

    一.linux文件属性 用户组概念:假如主机有两个团体,第一个团体名为projecta,里面有class1,class2,class3:第二个团体名为projecb,里面有class4,class5, ...

  3. 字符串的长度超过了为 maxJsonLength 属性设置的值

    当出现类似标题的错误时,可以按照如下方法解决: 1. 检查是否传递的JSON字符串长度过长 2.增加JSON串的长度设置,设置如下: <system.web.extensions>     ...

  4. 使用Flex构建Web和移动参考应用程序

    范例文件 Shopping Cart Sales Dashboard Expense Tracker 需要的其他产品 Android 2.2及更高版本或Android 3.0及更高版本的设备 仅仅在F ...

  5. iOS多线程的七大对象理解

    1用面向对象的观点去理解,进程和线程,同步和异步,并行和串行,还有主线程的主队列,的七者关系 进程:程序不运行时就是一堆代码,运行时就是一堆的进程的组合,进程是程序运行的基本单位. 线程:线程是进程的 ...

  6. 为苹果ATS和微信小程序搭建 Nginx + HTTPS 服务

    昨天测试开发微信小程序,才发现微信也要求用HTTPS加密数据,想来是由于之前苹果的ATS审核政策的缘故吧,微信想在苹果上开放小程序必然也只能要求开发者必须使用HTTPS了,于是在服务器上测试安装Ngi ...

  7. 在Oracle中数据库、表空间、表之间的关系

    在oracle中,表空间是存储概念上的,建立表空间需要有对应的数据文件,数据文件建立好之后直接会把一定的磁盘空间分配给它,这样可以对数据库的存储空间进行有效的管理.然后在建表的时候指定对应的表空间,该 ...

  8. 深入探讨 ECMAScript 规范第五版

    深入探讨 ECMAScript 规范第五版 随着 Web 应用开发的流行,JavaScript 越来越受到开发人员的重视.作为 ECMAScript 的变体,JavaScript 语言的很多语法特性都 ...

  9. C# 6 与 .NET Core 1.0 高级编程 - C# 6 改进

    个人原创译文,转载请注明出处.有不对的地方欢迎指出与交流. 英文原文:Professional C# 6 and .NET Core 1.0 - What's New in C# 6 C# 6 改进 ...

  10. Java如何判断字符串中包含有全角,半角符号

    首先介绍下全角跟半角之间的区别: 在计算机屏幕上,一个汉字要占两个英文字符的位置,人们把一个英文字符所占的位置称为"半角",相对地把一个汉字所占的位置称为"全角" ...