原文 Contoso 大学 - 2 – 实现基本的增删改查

目录

Contoso 大学 - 使用 EF Code First 创建 MVC 应用

原文地址:http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/implementing-basic-crud-functionality-with-the-entity-framework-in-asp-net-mvc-application

在上一个课程中,你已经创建了 MVC 应用,使用 EF 和 SQL Server Compact 保存和显示数据。在这个课程中,你将要复习并定制 MVC 脚手架为你的控制器和视图自动创建的 CRUD (创建、读取、更新和删除)代码。注意:为了在你的控制器和数据访问层之间进行抽象,通常的做法是实现仓储模式。为了保持这个课程的简洁,在这个系列的最后课程之前,我们不会实现仓储模式。

在这个课程中,你将要创建如下的页面。

2-1 创建详细页

脚手架创建的代码遗留下了学生注册属性没有处理,因为这个属性是集合属性。在详细页面中,你将要在 HTML 表格中显示这个集合的内容。

Controllers\StudentController.cs 中,详细页面的 Action 方法类似如下的代码:

public ViewResult Details(int id)
{
Student student = db.Students.Find(id);
return View(student);
}

代码使用 Find 方法来获取单个的 Student 实体,使用传递给方法的 id 关键字。Id 来自 Index 页面中的超级链接提供的查询字符串。

打开 Views\Student\Details.cshtml。每个字段使用 DisplayFor 助手方法进行显示,类似如下所示:

<div class="display-label">LastName</div>
<div class="display-field">
@Html.DisplayFor(model => model.LastName)
</div>

为了显示注册课程列表,在注册日期 EnrollmentDate 字段之后,fieldset 结束标记之前,增加如下的代码。

<div class="display-label">
@Html.LabelFor(model => model.Enrollments)
</div>
<div class="display-field">
<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>
</div>

代码遍历导航属性 Enrollments 中所有的实体,对于这个属性中的每一个 Enrollment 实体,将显示其中的课程和年级。课程标题通过 Enrollments  导航属性保存的 Course  实体来获得。在需要的时候,所有的数据从数据库中获取。( 从另外一个角度说,在这里使用了延迟加载,你没有为 Courses 导航属性指定饿汉模式加载,所以,在你第一次试图访问这个属性的时候,将会向数据库发出一次查询来获取数据,你可以在这个系列后面的 读取相关数据 部分获取更加详细的说明 )

运行这个页面,选择 Students 选项卡,然后点击 Details 超级链接,你就可以看到课程的列表。

2-2 建立创建页面

Controllers\StudentController.cs使用下面的代码替换HttpPostCreate 这个 Action 方法,为脚手架创建的代码增加 try-catch 块。

[HttpPost]
public ActionResult Create(Student student)
{
try
{
if (ModelState.IsValid)
{
db.Students.Add(student);
db.SaveChanges();
return RedirectToAction("Index");
}
}
catch (DataException)
{
//Log the error (add a variable name after DataException)
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator.");
}
return View(student);
}

这些代码将通过 ASP.NET MVC 模型绑定创建的实体对象加入到 Students 集合中,然后保存修改到数据库中。( 模型绑定是 ASP.NET MVC 的一个功能用于简化你获取通过表单提交的数据,模型绑定转换提交的表单数据到 .NET 中的数据类型,通过 Action 方法的参数传递进来,在这里,模型绑定通过表单数据为你实例化了一个 Student 的实体对象实例 )

这里的 try-catch 块是这些代码区别于脚手架创建的代码的唯一不同之处,在保存数据的时候,如果派生自DataException 的异常被抛出,错误信息将会被显示出来,这类错误典型的是由于一些内部错误,而不是程序错误,所以建议用户再重新试一次。在 Views\Student\Create.cshtml 中的代码与 Details.cshtml 中类似,除了将每个字段的 DisplayFor 代替为EditorFor 和 ValidationMessageFor 助手方法.下面的示例演示了相关的代码。

<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.cshtml. 中不需要进行修改。

重新运行页面,选择 Students选项卡,点击 Create New

默认会进行数据验证,输入名字和一个错误的日期,然后点击 Create,查看一下错误提示。

现在,你会看到通过 JavaScript 实现的客户端验证,但是,服务器端的验证也已经实现了。即使客户端验证失败了,坏的数据也会被捕获到,在服务器端会抛出一个异常。

将日期修改为一个正确的日期,例如:9/1/2005,然后点击 Create,会看到一个新的学生出现在 Index页面上。

2-3 创建一个编辑页面

在 Controllers\StudentController.cs 中,Http Edit 方法 ( 其中没有使用 HttpPost标签的那一个 ) 使用 Find 方法来获取选中的学生实体,像你在 Details 方法中看到的一样,不需要修改这个方法。

实际上,需要修改 HttpPost Edit 方法,使用下面的代码为它增加 try-catch处理。

[HttpPost]
public ActionResult Edit(Student student)
{
try
{
if (ModelState.IsValid)
{
db.Entry(student).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
}
catch (DataException)
{
//Log the error (add a variable name after DataException)
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator.");
}
return View(student);
}

这段代码非常类似在 Create 中的代码,实际上,除了将通过模型绑定创建的对象添加到实体集中,这段代码还设置了实体的标志来表示这个实体已经被修改过了。当 SaveChanges方法被调用的时候,数据库中行的所有字段都将会更新。包括用户没有修改过的字段,并发冲突被忽略掉 ( 你将会在这个系列的后面学习如何处理并发问题 )。

实体的状态,连接以及 SaveChanges 方法

数据库上下文对象维护内存中的对象与数据库中数据行之间的同步。这些信息在调用 SaveChanges方法被调用的时候使用。例如,当使用 Add 方法传递一个新的实体对象时,实体的状态被设置为 Added,在调用 SaveChanges方法的时候,数据库上下文使用 SQL 命令 Insert来插入数据。

实体的状态可能为如下之一:

Added. 实体在数据库中不存在。SaveChanges 方法必须执行 Insert 命令

Unchanged. 在调用 SaveChanges 的时候不需要做任何事情,当从数据库读取数据的时候,实体处于此状态。

Modified. 某些或者全部的实体属性被修改过. SaveChanges方法需要执行 Update 命令。

Deleted. 实体标记为已删除,SaveChanges 方法必须执行 Delete 命令。

Detached. 实体的状态没有被数据库上下文管理。

在桌面应用中,实体的状态改变典型地自动完成。在这种类型的应用中,你读取一个实体,然后修改某些属性的值,这使得实体的状态被自动修改为 Modified。然后,在调用 SaveChanged 的时候,实体框架生成 Update 命令进行更新,只有你修改的实际属性被更新。

但是,在 Web 应用程序中,这个处理序列不是连续的。因为数据库上下文读取的实体对象实例在页面被呈现之后被丢弃了。当 HttpPost Edit 方法被调用的时候,导致一个新的请求和一个新的数据库上下文对象被生成,所以,你必须手工设置实体的状态为 Modified。然后调用 SaveChanges 方法,实体框架更新数据库中数据行的所有列,因为数据库上下文没有办法知道你修改了哪些属性。

如果你希望 Update 语句仅仅更新你实际上修改的字段。你可以通过某种途径保存原有的数据值 ( 例如通过隐藏域 ),以便在 HttpPost Edit 方法被调用的时候这些值存在。然后,可以使用原始的数据来创建一个 Student 实体,使用 Attach 方法调用连接含有原始值的实体对象,然后,使用新的值来更新实体对象,最后再调用 SaveChanges 方法,更多的详细内容,可以查看 EF 团队的博客: Add/Attach and Entity StatesLocal Data

在 Views\Student\Edit.cshtml中的代码类似于 Create.cshtml ,不需要修改。

运行页面,选择 Students 选项卡,然后点击 Edit 超级链接。

修改一些数据,然后点击 Save,可以在 Index 页面看到修改后的数据。

2-4  创建删除页面

Controllers\StudentController.cs, 模板生成的 HttpGet Delete 方法使用 Find 方法来获取 Student 实体,像在Details 和 Edit 方法中一样。实际上,为了实现在调用 SaveChanges 方法失败的时候使用的错误页面,你需要为这个方法和相应的视图增加一些功能。

像在更新和创建操作中一样,删除操作也需要两个方法。GET 方法用于显示一个视图,使用户可以允许或者取消删除操作,如果用户允许删除操作,那么,将会发出一个 Post 请求,HttpPost Delete 方法将会被调用,然后执行实际的删除操作。

你需要为 HttpPost Delete 方法增加一个 try-catch 块来捕获数据库更新过程中的任何异常。如果错误出现,HttpPost Delete 方法调用 HttpGet Delete 方法,传递一个参数表示错误发生了。HttpGet Delete 方法就会根据错误信息重新显示确认页面,使用户可以取消或者重试。

使用下面的代码替换 HttpGet Delete 方法中的代码,这里会管理错误报告。

public ActionResult Delete(int id, bool? saveChangesError)
{
if (saveChangesError.GetValueOrDefault())
{
ViewBag.ErrorMessage = "Unable to save changes. Try again, and if the problem persists see your system administrator.";
}
return View(db.Students.Find(id));
}

这段代码接收一个可选的 bool 类型参数,这个参数用来表示是在更新失败之后调用这个方法。在页面请求中被调用的时候,这个参数为 null ( false ),当通过 HttpPost Delete 方法更新数据库失败后,被调用的时候,参数被设置为 true,错误信息被传递到视图中。

将 HttpPost Delete 方法 ( 名为 DeleteConfirmed 方法 )的代码替换成下面的代码,这将会执行实际的删除操作,并捕获任何数据库更新错误。

[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(int id)
{
try
{
Student student = db.Students.Find(id);
db.Students.Remove(student);
db.SaveChanges();
}
catch (DataException)
{
//Log the error (add a variable name after DataException)
return RedirectToAction("Delete",
new System.Web.Routing.RouteValueDictionary {
{ "id", id },
{ "saveChangesError", true } });
}
return RedirectToAction("Index");
}

这段代码获取选中的实体,然后调用 Remove 方法将实体的状态设置为 Deleted。当调用 SaveChanged 的时候,SQL 命令 Delete 被生成并执行。

如果性能是应用的高优先级目标,你可以省略掉不需要的 SQL 查询处理,使用下面的代码行调用 Find 和 Remove 方法。

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

这段代码实例化了一个 Student 实体,仅仅设置了主键的值,然后将实体的状态设置为 Deleted。EF 删除实体仅仅需要这些信息。

需要注意的是,HttpGet Delete 方法并不真正删除数据,在 GET 请求处理中进行删除存在着安全风险 ( 同样在进行编辑,创建,或者任何修改数据的处理中 ),更多的资料,参见:ASP.NET MVC Tip #46 — Don't use Delete Links because they create Security Holes

在 Views\Student\Delete.cshtml 中,在 h2 和 h3 之间增加下面的代码:

<p class="error">@ViewBag.ErrorMessage</p>

运行页面,选择 Students 选项卡,点击 Delete 超级链接。

点击 Delete,Index 页面中就不会再显示被删除的学生了。( 在处理并发的部分可以看到错误处理 )

2-5  确认数据库连接没有忘记关闭

为了确认数据库连接被正确关闭,以及资源被正确释放,你需要释放数据库上下文,这就是为什么在 StudentController 代码的最后会看到 Dispose 方法的原因,在 StudentController.cs, 如下所示:

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

基类 Controller 已经实现了接口 IDisposable,所以这段代码简单地重写了 Dispose ( bool ) 方法来显式释放上下文对象。

你现在已经有了一套完整的页面对 Student 进行增、删、改、查处理。在下一课程中,将会为 Index 页面增加排序和分页功能。

Contoso 大学 - 2 – 实现基本的增删改查的更多相关文章

  1. mongoDB4--mongoDB的增删改查

    MongoDb基本操作之增删改查我们知道传统关系型数据库的最常用操作就是"增加/删除/修改/查询",也就是传说中的CRUD(create/remove/updte/delete). ...

  2. C#利用WinForm调用WebServices实现增删改查

    实习导师要求做一个项目,用Winform调用WebServices实现增删改查的功能.写下这篇博客,当做是这个项目的总结.如果您有什么建议,可以给我留言.欢迎指正. 1.首先,我接到这个项目的时候,根 ...

  3. [Android] SQLite数据库之增删改查基础操作

        在编程中常常会遇到数据库的操作,而Android系统内置了SQLite,它是一款轻型数据库,遵守事务ACID的关系型数据库管理系统,它占用的资源非常低,可以支持Windows/Linux/Un ...

  4. JAVAWEB之增删改查

    青年志愿者服务网(20分)   1.项目需求: 为了适应社会主义市场经济发展的需要,推动青年志愿服务体系和多层次社会保障体系的建立和完善,促进青年健康成长,石家庄铁道大学急需建设青年志愿者服务网,推进 ...

  5. java程序设计课期中考试——数据库的增删改查和简单的js界面

    首先是设计思路,对于数据库的增删改查,我们借助Ecilipse来进行前端和后端的编写.Ecilipse是可以进行java web项目的操作的. 前端,我们选择用使用jsp,所谓的jsp就是可以嵌入其他 ...

  6. Sqlserver 增删改查----增

    注意我说的常见查询,可不是简单到一个表得增删改查,做过实际开发得人都知道,在实际开发中,真正牵扯到一个表得增删改查只能说占很小得一部分,大多都是好几个表的关联操作的. 下面我就说一下我在实际开发中经常 ...

  7. django 利用ORM对单表进行增删改查

    牛小妹上周末,一直在尝试如何把数据库的数据弄到界面上.毕竟是新手,搞不出来,文档也看不懂.不过没关系,才刚上大学.今晚我们就来解释下,要把数据搞到界面的第一步.先把数据放到库里,然后再把数据从库里拿出 ...

  8. [刘阳Java]_程序员Java编程进阶的5个注意点,别编程两三年还是增删改查

    此文章也是关注网上好几篇技术文章后,今天分享出来.因为,总有在程序学习路上的小伙伴会感到迷茫.而迷茫存在的情况如下 第一种:在大学学习中出现的迷茫,不知道Java到底要学什么.学习Java的标准是什么 ...

  9. Dapper逆天入门~强类型,动态类型,多映射,多返回值,增删改查+存储过程+事物案例演示

    Dapper的牛逼就不扯蛋了,答应群友做个入门Demo的,现有园友需要,那么公开分享一下: 完整Demo:http://pan.baidu.com/s/1i3TcEzj 注 意 事 项:http:// ...

随机推荐

  1. ALT(预警)

    1. Alert简介 Alert是一种Oracle系统中的一种机制,它可以监视系统数据库,在规定的情况下给规定用户一个通知,通知可以是邮件或者其他形式,在标注的系统和客户化系统中都是可以定义使用的 2 ...

  2. Bootstrap排版

    <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta na ...

  3. ADO.NET 快速入门(十):过滤数据

    我们有很多方法来过滤数据.一种是在数据库命令级别,利用 where 子句查询过滤数据.另一种是在数据填充到 DataSet 以后过滤数据.本篇讨论后者.   一旦数据填充到 DataSet,你可以使用 ...

  4. LFI漏洞利用总结(转载)

    主要涉及到的函数include(),require().include_once(),require_once()magic_quotes_gpc().allow_url_fopen().allow_ ...

  5. PostgreSQL的 initdb 源代码分析之十三

    继续分析: /* Bootstrap template1 */ bootstrap_template1(); 展开: 我这里读入的文件是:/home/pgsql/project/share/postg ...

  6. Codeforces Round #331 (Div. 2) D. Wilbur and Trees 记忆化搜索

    D. Wilbur and Trees Time Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://codeforces.com/contest/596/p ...

  7. URAL 1775 B - Space Bowling 计算几何

    B - Space BowlingTime Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://acm.hust.edu.cn/vjudge/contest/ ...

  8. w3cmark前端精彩博文周报 10.13-10.19

    w3cmark推出每周精选前端博文推荐,通过阅读别人的代码,学习别人的经验,提升自己的水平.欢迎关注 @前端笔记网 微博.其实如果是关注我们微博的朋友都对下面的文章熟悉,因为我们会一旦发现有价值的.精 ...

  9. 基于Linux的owncloud搭建

    为了保证一个纯净的环境,我重新安装了一台centos系统 [root@localhost httpd-2.2.23]# lsb_release -a LSB Version:    :base-4.0 ...

  10. C#_模拟webAp_POST-GET-PUT-DELETE

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.N ...