EF6学习笔记总目录 ASP.NET MVC5 及 EF6 学习笔记 - (目录整理)
接上篇:
EF6 学习笔记(一):Code First 方式生成数据库及初始化数据库实际操作
本篇原文链接:
Implementing Basic CRUD Functionality
说明:学习笔记参考原文中的流程,为了增加实际操作性,并能够深入理解,部分地方根据实际情况做了一些调整;并且根据自己的理解做了一些扩展。
本人的学习环境: VS2017 + EF 6.1.3 + .NET 4.6.1
上一篇已完成数据库创建、基本的List页面显示;本篇操练如何进行增删改查;
第一步: Read (Details)
这个相对比较简单,只是显示详细信息,具体显示的一些技巧还是要学习 MVC的View的语法和用法;这里只看一个基本功能;
为Student控制器新建Details Action: (通过请求送进来的ID参数来检索到Student实例,再送到View里去显示;)
public ActionResult Details(int? id) { if (id == null) { return new HttpStatusCodeResult(System.Net.HttpStatusCode.BadRequest); } Student student = db.Students.Find(id); if (student == null) { return HttpNotFound(); } return View(student); }
为Details Action新增View : (Template名字就是Details , 强类型选择Student , 上下文选 SchoolContext)
默认情况下,学生的课程记录不会显示出来;如下:
把View加入以下代码:
<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>
则可以显示该学生的课程记录:
第二步: Create
Create操作是有两个Action完成的,一个Get Action用来显示新建页面,一个Post Action 用来处理新增;
Get Action比较简单:
public ActionResult Create() { return View(); }
然后为Create Action新增Create View ( Template 选择 Create , 强类型选择Student, 上下文选择 SchoolContext)
在这个实验里可以不用删除输入项,在具体项目中可根据实际需要进行输入的项目进行保留,其他不需要用户输入的,则可以从View中删除;
这里就带来了一个设定,为了防止有人恶意地在提交的请求数据中加入我们不希望用户输入的字段值,那么如果不做控制,则这个值就会被写入数据库中;
这就带来的风险,所以在 Post Creat Action中,增加了 [Bind(Include = "LastName, FirstMidName, EnrollmentDate")] 这个限制;
当然既然可以采用Include 限制,那么也可以用exclude来表示除了某某字段;
[HttpPost] [ValidateAntiForgeryToken] public ActionResult Create([Bind(Include = "LastName, FirstMidName, EnrollmentDate")]Student student) { try { if (ModelState.IsValid) { db.Students.Add(student); db.SaveChanges(); return RedirectToAction("Index"); } } catch (DataException /* dex */) { //Log the error (uncomment dex variable name and add a line here to write a log. ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator."); } return View(student); }
至于如何进行Model数据验证,则可以参考MVC的一些资料;(例如限制非空、限制长度、正则表达式、以及自定义Remote验证)
另外,为Action增加[ValidateAntiForgeryToken] 验证属性,配合View中@Html.AntiForgeryToken() 来防止 cross-site request forgery 跨站请求伪造;
第三步:Edit (Update)
Edit也是分为两个Action, 一个是HttpGet Action为了取现有数据来显示,一个是HttpPost Action为了把修改后的值传回去;
先手动增加 HttpGet Edit Action:
public ActionResult Edit(int? id) { if (id == null) { return new HttpStatusCodeResult(System.Net.HttpStatusCode.BadRequest); } Student student = db.Students.Find(id); if (student == null) { return HttpNotFound(); } return View(student); }
然后创建一个强类型的View:
这个View基本可以不用改动,在实际项目中,可以进行调整显示;
重点是 HttpPost的Edit Action , 原文中的标准写法:
[HttpPost, ActionName("Edit")] [ValidateAntiForgeryToken] public ActionResult EditPost(int? id) { if (id == null) { return new HttpStatusCodeResult(System.Net.HttpStatusCode.BadRequest); } var studentToUpdate = db.Students.Find(id); if (TryUpdateModel(studentToUpdate, "", new string[] { "LastName", "FirstMidName", "EnrollmentDate" })) { try { db.SaveChanges(); return RedirectToAction("Index"); } catch (DataException /* dex */) { //Log the error (uncomment dex variable name and add a line here to write a log. ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator."); } } return View(studentToUpdate); }
首先,他Action Name没有直接定义为Edit, 而是通过属性里通过ActioName强制定义为Edit;
其次,一开始觉得比较神奇的是,参数里只写了 int? id ,按照以往的理解,这个地方应该直接通过模型绑定,和Create一样直接输入一个Student的实例啊;看到这里确实有点蒙;
不过,仔细研究了下面那个TryUpdateModel后就能理解了,这个方法有10多种多载,实现手段也就非常灵活,具体资料需要再参考 TryUpdateModel 的详细说明;
原文例子中,通过第3个参数实现了数据更新字段的限定,即如果模型有10来个字段,但允许用户编辑的只有3个,那么通过 new string[] { "LastName", "FirstMidName", "EnrollmentDate" } 这个参数可以限制哪3个字段可以被更新;其他的保持不变;
另外一种Edit写法:
[HttpPost] [ValidateAntiForgeryToken] public ActionResult Edit([Bind(Include = "ID,LastName,FirstMidName,EnrollmentDate")] Student student) { if (ModelState.IsValid) { db.Entry(student).State = EntityState.Modified; db.SaveChanges(); return RedirectToAction("Index"); } return View(student); }
第四步:Delete
删除相对比较简单,也是一个 HttpGet Delete 和一个HttpPost Delete Action;
HttpGet Delete Action:
public ActionResult Delete(int? id, bool? saveChangesError = false) { if (id == null) { return new HttpStatusCodeResult(System.Net.HttpStatusCode.BadRequest); } if (saveChangesError.GetValueOrDefault()) { ViewBag.ErrorMessage = "Delete failed. Try again, and if the problem persists see your system administrator."; } Student student = db.Students.Find(id); if (student == null) { return HttpNotFound(); } return View(student); }
增加的 bool? saveChangesError = false 是了httpPost Delete Action在发生错误的时候,重新用httpget Delete Action来显示相关错误信息;
HttpPost Delete Action:
[HttpPost] [ValidateAntiForgeryToken] public ActionResult Delete(int id) { try { //方法一 //Student student = db.Students.Find(id); //db.Students.Remove(student); //db.SaveChanges(); //方法二 Student studentToDelete = new Student() { ID = id }; db.Entry(studentToDelete).State = EntityState.Deleted; db.SaveChanges(); } catch (DataException/* dex */) { //Log the error (uncomment dex variable name and add a line here to write a log. return RedirectToAction("Delete", new { id = id, saveChangesError = true }); } return RedirectToAction("Index"); }
方法1比方法2效率低一点;方法一需要先检索一下数据库,然后再执行删除操作;
但是方法二只能用主关键字来进行登记删除状态,好处是效率高一点;
最后一步:关闭数据库连接
通过重载Dispose实现:
protected override void Dispose(bool disposing) { db.Dispose(); base.Dispose(disposing); }