MVC5+EF6--2 实现基本的CRUD功能

近期学习MVC5+EF6,找到了Microsoft的原文,一个非常棒的系列,Getting Started with Entity Framework 6 Code First using MVC 5,网址:http://www.asp.net/mvc/overview/getting-started/getting-started-with-ef-using-mvc/creating-an-entity-framework-data-model-for-an-asp-net-mvc-application。

这个系列的原文,可以上面网址找到。我也从网上找到了相关的译文。申明,这些译文,我不是原创,而是从网上找来的,为避免下次还要网上查询,现将这些译文整理放在上面。以后找时间将原文地址附上

Contoso University示例网站演示如何使用Entity Framework 5创建ASP.NET MVC 4应用程序。Entity Framework有三种处理数据的方式: Database First, Model First, and Code First. 本指南使用代码优先。其它方式请查询资料。示例程序是为Contoso University建立一个网站。功能包括:学生管理、课程创建、教师分配。本系列指南逐步讲述如何实现这一网站程序。

本示例程序基于 ASP.NET MVC.如果使用 ASP.NET Web Forms model, 请查看 Model Binding and Web Forms系列指南和 ASP.NET Data Access Content Map.

如有问题,可在这些讨论区提问: ASP.NET Entity Framework forum, the Entity Framework and LINQ to Entities forum, or StackOverflow.com.

在上一篇指南中创建了一个MVC程序,使用EFSQL Server LocalDB存储、展示数据。这一指南回顾并自定义CRUD代码,之前的代码是MVC结构自动创建的控制器和视图代码.

Note  通常的做法是在控制器和数据访问层之间使用仓库模式创建一个抽象层。这里暂时不实现这一功能,后续再进行补充。请查看 (Implementing the Repository and Unit of Work Patterns).

本指南将创建如下页面:

                       

 

创建详情页面

Index 页面的scaffolded代码不包括 Enrollments 属性, 因为其是集合. 在Details 页面将在 HTML 表中列出此集合的内容.

在Controllers\StudentController.cs,  Details 视图的行为方法使用  Find 检索获取一个 Student 实体.

public ActionResult Details(int id = 0)
{
    Student student = db.Students.Find(id);
    if (student == null)
    {
        return HttpNotFound();
    }
    return View(student);
}

主键的值通过地址路由参数传递

  1. 打开 Views\Student\Details.cshtml. 每个字段通过DisplayFor 显示:
  2. 在EnrollmentDate 之后,在 fieldset标记结束之前, 添加如下代码显示注册信息列表:
2.      <div class="display-label">
3.           @Html.DisplayNameFor(model => model.LastName)
4.      </div>
5.      <div class="display-field">
6.          @Html.DisplayFor(model => model.LastName)
    </div>
8.       <div class="display-label">
9.          @Html.LabelFor(model => model.Enrollments)
10.            </div>
11.            <div class="display-field">
12.                <table>
13.                    <tr>
14.                        <th>Course Title</th>
15.                        <th>Grade</th>
16.                    </tr>
17.                    @foreach (var item in Model.Enrollments)
18.                    {
19.                        <tr>
20.                            <td>
21.                                @Html.DisplayFor(modelItem => item.Course.Title)
22.                            </td>
23.                            <td>
24.                                @Html.DisplayFor(modelItem => item.Grade)
25.                            </td>
26.                        </tr>
27.                    }
28.                </table>
29.            </div>
30.        </fieldset>
31.        <p>
32.            @Html.ActionLink("Edit", "Edit", new { id=Model.StudentID }) |
33.            @Html.ActionLink("Back to List", "Index")
</p>

循环显示Enrollments导航属性中的每一个Enrollment实体,其中显示的课程名是通过Enrollment中的导航实体Course找到的。所有数据在需要时从数据库自动检索。也就是说,这里使用了延迟加载。无需添加代码,在第一次使用此属性时,数据从数据库中检索得到。随后在 Reading Related Data 进一步介绍延迟加载

  1. 点击列表中某一学生,即可看到详情信息如下:

更新Create页面

  1. Controllers\StudentController.cs, 替换 HttpPost Create  方法的代码,添加try-catch   Bind attribute:
2.  [HttpPost]
3.  [ValidateAntiForgeryToken]
4.  public ActionResult Create(
5.     [Bind(Include = "LastName, FirstMidName, EnrollmentDate")]
6.     Student student)
7.  {
8.     try
9.     {
10.              if (ModelState.IsValid)
11.              {
12.                 db.Students.Add(student);
13.                 db.SaveChanges();
14.                 return RedirectToAction("Index");
15.              }
16.           }
17.           catch (DataException /* dex */)
18.           {
19.              //Log the error (uncomment dex variable name after DataException and add a line here to write a log.
20.              ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator.");
21.           }
22.           return View(student);
}

代码将ASP.NET MVC模型绑定器生成的 Student 实体添加到 Students 实体集并保存到数据库. (模型绑定器指 ASP.NET MVC 的一项功能:使你更容易处理表单提交的数据; 模型绑定器将提交的表单数据转为  CLR 类型并作为参数传递到行为方法.本例中, 模型绑定器生成一个Student 实体,属性值来自于 Form 集合.)

ValidateAntiForgeryToken 特性阻止 cross-site request forgery 攻击.

安全提示: Bind 特性避免over-posting. 例如, 如果 Student实体包含 Secret 属性,此属性不想通过此页面更新.

   public class Student
   {
      public int StudentID { get; set; }
      public string LastName { get; set; }
      public string FirstMidName { get; set; }
      public DateTime EnrollmentDate { get; set; }
      public string Secret { get; set; }
 
      public virtual ICollection<Enrollment> Enrollments { get; set; }
   }

即便本页面没有Secret 输入区域, 黑客可使用工具如 asfiddler, 或者JavaScript, 提交一个 Secret 表单值. 没有 Bind 特性对区域的限制,模型绑定器将在创建Student使用此表单值,黑客提交的内容将存入数据库. 下图显示通过工具添加Secret 区域(with the value "OverPost") 并提交表单信息.

虽然你没打算通过页面更新此属性,"OverPost" 将成功的把 Secret 属性的值添加到数据中.

安全的做法是使用Bind 特性的 Include 参数添加白名单.也可以使用 Exclude 添加你想排除的黑名单. 使用 Include是因为更安全,当实体添加一个新属性,  Exclude 列表不会自动包含此属性.

还有一种方法,很多人喜欢用,在模型绑定时使用视图模型. 视图模型只包含想绑定的属性. 一旦 MVC 绑定器完成工作,你再将视图模型中的属性值赋给实体对象.

除了Bind 特性, try-catch 是对默认代码做的另一改变. 如果异常来自 DataException , 将显示一个错误提示信息. DataException 异常可能来自于其它方面而非编程错误, 因此用户可尝试再次执行. 本指南没有涉及的内容是: 使用如 ELMAH产品质量程序记录错误日志.

Views\Student\Create.cshtml 代码和Details.cshtml的代码相似, 除了使用 EditorFor 和ValidationMessageFor 帮助器而非 DisplayFor. 相关代码如下:

<div class="editor-label">
    @Html.LabelFor(model => model.LastName)
</div>
<div class="editor-field">
    @Html.EditorFor(model => model.LastName)
    @Html.ValidationMessageFor(model => model.LastName)
</div>

Create.chstml 也包括 @Html.AntiForgeryToken(), 其和控制器的 ValidateAntiForgeryToken特性一同阻止cross-site request forgery 攻击.

无需改变 Create.cshtml.

  1. 运行程序选择创建学生.

数据验证默认在起作用. 输入姓名和错误的日期点击创建,会看到提示信息.

此例中你看到JavaScript 实现的 通过客户端验证 (9月只有30天, 因此日期无效). 但服务器端得验证实现了 .即便客户端验证不起作用, 也能捕获坏的数据 (模型将无效),将转向Create 视图. 你可以通过禁用浏览器的JavaScript测试一下. 下面高亮代码显示了有效性判断.

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Student student)
{
    if (ModelState.IsValid)
    {
        db.Students.Add(student);
        db.SaveChanges();
        return RedirectToAction("Index");
    }
 
    return View(student);
}

将日期改成有效值,如 9/1/2005 点击创建,则此学生信息将加入到Index的学生列表.

更新 Edit POST页面

在 Controllers\StudentController.cs, HttpGet Edit 方法(没有HttpPost 特性的那个方法) 使用 Find方法检索到 Student 实体, 如在 Details 方法所见. 无需修改代码.

但 HttpPost Edit 行为方法的代码需要添加 try-catchBind 特性:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(
   [Bind(Include = "StudentID, LastName, FirstMidName, EnrollmentDate")]
   Student student)
{
   try
   {
      if (ModelState.IsValid)
      {
         db.Entry(student).State = EntityState.Modified;
         db.SaveChanges();
         return RedirectToAction("Index");
      }
   }
   catch (DataException /* dex */)
   {
      //Log the error (uncomment dex variable name after DataException 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);
}

此代码和 HttpPost Create 方法代码很像. 但是, 模型绑定器创建的对象不是添加到实体集, 而是通过设置flag告诉实体集,此实体发生了变化.当 SaveChanges 被调用, Modified flag 使得 Entity Framework 创建更新数据行的 SQL语句. 数据行的所有列都将更新,包括没有改变的那些列, 忽略并发冲突. 在 Handling Concurrency 将学习如何处理兵法冲突.)

实体状态、依附和 SaveChanges 方法

数据库上下文负责跟踪内存中的实体和数据库中的数据是否同步, 这将决定执行 SaveChanges 方法时的工作. 例如, 如果像 Add 方法传递了一个新的实体, 该实体状态将被设置为 Added. 当调用SaveChanges 方法,数据库上下文执行 SQL INSERT 命令.

实体可能处于以下状态:

  • Added. 数据库中不存在. SaveChanges 方法执行 INSERT.
  • Unchanged. 执行 SaveChanges 时什么也不用做. 当从数据库刚被读取出来时,实体为此状态.
  • Modified. 实体的某些属性值被改变.  SaveChanges 执行 UPDATE .
  • Deleted. 实体被标记为删除. SaveChanges 执行 DELETE .
  • Detached. 实体未被数据库上下文跟踪.

在桌面程序,状态的改变是自动发生的. 在桌面程序 读取一个实体并修改其属性值. 实体状态自动设置为Modified. 当调用 SaveChanges, Entity Framework 生成 SQL UPDATE 语句只更新发生改变的属性值.

连接断开是web程序的特点. DbContext 读取数据后页面呈现完毕即被释放. 当 HttpPost Edit 行为方法被调用时,  产生新的请求生成新的 DbContext实例, 因此你必须设置实体状态为 Modified.然后当调用 SaveChanges, Entity Framework 更新数据行的所有列,因为数据上下文不知道哪些列的值发生变化.

如果希望 SQL Update 语句只更新变化了的数据列,可通过一些方法保存原始值 (如 hidden fields) 当调用 HttpPost Edit 方法时可使用这些值. 然后使用这些原始值生成 Student 实体, 调用原有实体的 Attach方法, 然后调用 SaveChanges.更多信息请查看 Entity states and SaveChanges 和Local Data .

Views\Student\Edit.cshtml 的代码和 Create.cshtml代码相似, 无需改变.

运行查看效果.

修改一些值,然后保存,Index将看到新的值.

更新Delete 页面

在Controllers\StudentController.cs,  HttpGet Delete 方法使用 Find 检索选中的 Student 实体, 如你在 Details 和Edit 方法所见一样. 但是, 当 SaveChanges 执行失败时如果需要显示错误提示,需要向方法和相应的视图添加一些功能.

如你在update 和create 操作所见, delete 操作需要两个行为方法.  GET 提供用户查看详情和取消删除的功能 . 如果用户确认删除,  则触发POST.  HttpPost Delete 方法被调用删除将被执行.

请为HttpPost Delete添加 try-catch 处理可能由于数据库引起的异常. 如果出现异常, HttpPost Delete 调用 HttpGet Delete 方法, 向其传递一个表明错误的参数. HttpGet Delete 方法将附带错误信息重新显示删除确认页面, 让用户选中再试一次或者取消.

  1. HttpGet Delete 代码如下:
2.  public ActionResult Delete(bool? saveChangesError=false, int id = 0)
3.  {
4.      if (saveChangesError.GetValueOrDefault())
5.      {
6.          ViewBag.ErrorMessage = "Delete failed. Try again, and if the problem persists see your system administrator.";
7.      }
8.      Student student = db.Students.Find(id);
9.      if (student == null)
10.            {
11.                return HttpNotFound();
12.            }
13.            return View(student);
}

代码接受一个 optional 布尔参数,此参数标明是否附带错误信息.  HttpGet Delete 不包括错误信息时此参数值为false  . 当被 HttpPost Delete 调用时, 此参数为true 并向视图传递错误信息.

  1. HttpPost Delete 代码如下:
15.        [HttpPost]
16.        [ValidateAntiForgeryToken]
17.        public ActionResult Delete(int id)
18.        {
19.            try
20.            {
21.                Student student = db.Students.Find(id);
22.                db.Students.Remove(student);
23.                db.SaveChanges();
24.            }
25.            catch (DataException/* dex */)
26.            {
27.                // uncomment dex and log error. 
28.                return RedirectToAction("Delete", new { id = id, saveChangesError = true });
29.            }
30.            return RedirectToAction("Index");
}

代码检索选中的实体, 调用 Remove 方法将实体状态设为 Deleted. 当SaveChanges 执行时, 生成SQL DELETE 命令. 方法名由 DeleteConfirmed 改为Delete. 自动生成的代码相应 HttpPost Delete的是DeleteConfirmed 方法,该方法被设置了HttpPost  . ( The CLR 要求重载的方法要有不同的参数.) 既然方法签名(参数)已经改变,可对删除的 HttpPost 和HttpGet 使用相同的方法名.

改进性能对于一个大量数据的程序来说很有必要, 使用下面的代码替换调用Find Remove方法的代码,避免执行一次不必要的对数据的 SQL 查询:

Student studentToDelete = new Student() { StudentID = id };
db.Entry(studentToDelete).State = EntityState.Deleted;

代码仅使用主键初始化一个 Student 实体,并将该实体状态设为Deleted. 这就是 Entity Framework删除实体所需要的.

如前面提到的, HttpGet Delete没有删除数据. 通过 GET 请求执行删除操作(或者编辑、创建等其它引起数据变化的操作) 会引起风险. 更多信息请查看 ASP.NET MVC Tip #46 — Don‘t use Delete Links because they create Security Holes .

  1. 在 Views\Student\Delete.cshtml,在h2 和 h3 之间添加错误信息提示:
32.        <h2>Delete</h2>
33.        <p class="error">@ViewBag.ErrorMessage</p>
<h3>Are you sure you want to delete this?</h3>

运行程序:

  1. 点击删除 Index页面将显示删除后的学生列表. (在随后的 Handling Concurrency 中将看到异常的情况.)

确保关闭了数据库连接

为了确保数据库连接关闭而且由此占用的资源也被释放, 请确定释放了数据上下文的实例. 这是自动生成的StudentController 代码包含 Dispose 方法的原因, 如下所示:

protected override void Dispose(bool disposing)
{
    db.Dispose();
    base.Dispose(disposing);
}

Controller 基类已经实现了 IDisposable 接口, 此代码只是简单的添加了对 Dispose(bool) 方法的重载以释放数据上下文实例.

总结

已经创建了一系列的页面实现对Student 实体的 CRUD 操作. 使用 MVC 帮助器生成数据域的HTML代码. 更多有关 MVC helpers的信息, 请查看 Rendering a Form Using HTML Helpers (the page is for MVC 3 but is still relevant for MVC 4).

下一节将通过添加排序和分页扩展Index页面的功能.

其它 Entity Framework相关资源请查看 ASP.NET Data Access Content Map.

时间: 2024-11-03 08:27:52

MVC5+EF6--2 实现基本的CRUD功能的相关文章

MVC5 Entity Framework学习之实现主要的CRUD功能

在上一篇文章中,我们使用Entity Framework 和SQL Server LocalDB创建了一个MVC应用程序,并使用它来存储和显示数据.在这篇文章中,你将对由 MVC框架自己主动创建的CRUD(create, read, update, delete)代码进行改动. 注意:通常我们在控制器和数据訪问层之间创建一个抽象层来实现仓储模式.为了将注意力聚焦在怎样使用实体框架上.这里暂没有使用仓储模式. 在本篇文章中,要创建的web页面: watermark/2/text/aHR0cDovL

MVC5+EF6 入门完整教程十

本篇是第一阶段的完结篇. 学完这篇后,你应该可以利用MVC进行完整项目的开发了. 本篇主要讲述多表关联数据的更新,以及如何使用原生SQL. 文章提纲 多表关联数据更新 如何使用原生SQL 总结 多表关联数据更新 我们在第四篇文章已经讲过数据的更新了,不过那个是针对单表结构的更新. 这次我们讲下使用EF进行关联数据的更新. 关联数据更新有两种情况: 1.一对多 2.多对多 第一种情况关联表有主外键关联,只要简单的更新外键值就可以了(相当于更新单表),我们主要讲解第二种多对多的情况. 使用之前很熟悉

MVC5 + EF6 + Bootstrap3 (12) 新建数据

原文:MVC5 + EF6 + Bootstrap3 (12) 新建数据 Slark.NET-博客园 http://www.cnblogs.com/slark/p/mvc5-ef6-bs3-get-started-create.html 系列教程:MVC5 + EF6 + Bootstrap3 上一节:MVC5 + EF6 + Bootstrap3 (11) 排序.搜索.分页 源码下载:点我下载 目录 前言 新建链接 新建页面Action 新建页面View 添加数据Action 查看结果 结尾

【第一篇】ASP.NET MVC快速入门之数据库操作(MVC5+EF6)

目录 [第二篇]ASP.NET MVC快速入门之数据注解(MVC5+EF6) [第三篇]ASP.NET MVC快速入门之安全策略(MVC5+EF6) [第四篇]ASP.NET MVC快速入门之完整示例(MVC5+EF6) 请关注三石的博客:http://cnblogs.com/sanshi 新建项目 打开VS2015,找到菜单项[文件->新建->项目],打开向导对话框: 注意我们的选择项: 1.     运行平台:.NET FrameWork 4.5 2.     项目模板:ASP.NET W

构建ASP.NET MVC5+EF6+EasyUI 1.5+Unity4.x注入的后台管理系统(1)-前言与目录(持续更新中...)

前言: 起初写这个框架的时候,可以说在当时来说并不是很流行的设计模式,那是在2012年,面向对象的编程大家都很熟悉, 但是“注入.控制反转(DI,IOC,依赖注入).AOP切面编程”新兴名词 很多人并不知道特别是从事.NET开发的人,至少在当时 是这么样的,但是在今天它们却是非常流行的技术指标,很多大牛也承认,这是主流的开发模式,你们可以从招聘网的技术岗位看出. 我从事过MVC2.0到5.0的相关开发工作,见证了MVC的成熟演变过程,就像本框架一样,设计模式未曾改变,但是代码一直在重 构.我也坚

[实战]MVC5+EF6+MySql企业网盘实战(9)——编辑文件名

写在前面 上篇文章实现了文件的下载,本篇文章将实现编辑文件名的功能. 系列文章 [EF]vs15+ef6+mysql code first方式 [实战]MVC5+EF6+MySql企业网盘实战(1) [实战]MVC5+EF6+MySql企业网盘实战(2)——用户注册 [实战]MVC5+EF6+MySql企业网盘实战(3)——验证码 [实战]MVC5+EF6+MySql企业网盘实战(4)——上传头像 [Bootstrap]modal弹出框 [实战]MVC5+EF6+MySql企业网盘实战(5)——

MVC5+EF6 入门完整教程九

前一阵子临时有事,这篇文章发布间隔比较长,我们先回顾下之前的内容,每篇文章用一句话总结重点. 文章一 MVC核心概念简介,一个基本MVC项目结构 文章二 通过开发一个最基本的登录界面,介绍了如何从Controller中获取表单数据 文章三 EF的整个开发过程 文章四 EF基本的CRUD和常用的HtmlHelper 文章五 使用布局页(模板页)改造UI 文章六 分部视图(Partial View) 文章七 排序过滤分页 文章八 不丢失数据进行数据库结构升级 以上如果有不清楚的可以再回去看一下. 文

MVC5+EF6 入门完整教程

MVC5+EF6 入门完整教程11--细说MVC中仓储模式的应用 MVC5+EF6 入门完整教程10:多对多关联表更新&使用原生SQL@20150521 MVC5+EF6 入门完整教程9:多表数据加载@20150212 MVC5+EF6 入门完整教程8 :不丢失数据进行数据库结构升级 @20141215 MVC5+EF6 入门完整教程7 :排序过滤分页 @20141201 MVC5+EF6 入门完整教程6 :分部视图(Partial View) @20141117 MVC5+EF6 入门完整教程

MVC5 + EF6 简单示例

本文所使用的软件及环境: Visual Studio Ultimate 2013 (下载地址:http://www.visualstudio.com/downloads/download-visual-studio-vs); MVC5 + EF6 + .NET Framework 4.5 + LocalDB; Windows 7 x64 Professional 说明: 在EF (Entity Framework,以下简称EF6)框架下,操作数据的方式有三种:Database First, Mo