在上一篇文章中,我们使用Entity Framework 和SQL Server LocalDB创建了一个MVC应用程序,并使用它来存储和显示数据。在这篇文章中,你将对由 MVC框架自己主动创建的CRUD(create, read, update, delete)代码进行改动。

注意:通常我们在控制器和数据訪问层之间创建一个抽象层来实现仓储模式。为了将注意力聚焦在怎样使用实体框架上。这里暂没有使用仓储模式。

在本篇文章中,要创建的web页面:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvam9obnNvbmJsb2c=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">

1.创建一个Details页面

由框架代码生成的Students Index页面暂没有考虑Enrollments属性,由于该属性是一个集合。在Details页面中,我们将在HTML表格中显示集合中的内容。

打开 Controllers\StudentController.cs。能够看到相应Details视图的Details方法使用Find方法来检索单个学生实体:

public ActionResult Details(int?

id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Student student = db.Students.Find(id);
if (student == null)
{
return HttpNotFound();
}
return View(student);
}

Details方法的id參数来自Index页面中Details链接,称为路由数据(route data)。

路由数据是指在路由表中指定,通过URL传递,由模型绑定器接收的数据。

例如以下所看到的,默认路由指定了controller, action和 id

 routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

在以下的 URL中。默认路由将Instructor 映射为controller, Index映射为action, 1 映射为 id

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

"?

courseID=2021" 是查询字符串, 假设你将id作为查询字符串,模型绑定器也能正常解析

http://localhost:1230/Instructor/Index?id=1&CourseID=2021

在Razor视图中,由ActionLink语句来创建URL,如以下的代码中id參数匹配默认路由。所以id被作为进路由数据

 @Html.ActionLink("Select", "Index", new { id = item.PersonID  })

以下的代码中courseID參数 不匹配默认路由,所以courseID被作为查询字符串

@Html.ActionLink("Select", "Index", new { courseID = item.CourseID }) 

打开Views\Student\Details.cshtml,每一个字段都使用DisplayFor帮助器来显示数据,如以下的代码所看到的:

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

在EnrollmentData字段之后。</dl>标签之前,加入以下的代码

        <dt>
@Html.DisplayNameFor(model => model.EnrollmentDate)
</dt> <dd>
@Html.DisplayFor(model => model.EnrollmentDate)
</dd>
<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>
</dl>
</div>
<p>
@Html.ActionLink("Edit", "Edit", new { id = Model.ID }) |
@Html.ActionLink("Back to List", "Index")
</p>

假设代码缩进不对,能够使用Ctrl-K-D快捷键来纠正它。

上面的代码遍历Enrollments导航属性中的实体,对于每个Enrollment实体。显示出Course Title 和Grade。Course Title是从Enrollments实体中的Course导航属性中的Course实体中获取的,全部这些数据豆是在须要时自己主动从数据库检索的。(换句话说,这里使用的是延迟载入。你没有为Courses导航属性指定预先载入。所以在同一次查询中,仅仅检索了Students数据而没有检索enrollments数据。相反。在第一次试图訪问Enrollments导航属性时,会创建一个新的查询并发送到数据库。

执行项目,选择Students 选项卡并点击名为Alexander Carson的Details 链接。(假设你按Ctrl+F5,直接打开Details.cshtml,会得到HTTP 400错误,由于Visual Studio会直接打开Details页面却没有指定不论什么一个studen,路由匹配错误导致程序出错。在这样的情况下。你仅仅须要从URL中删除Student/Details然后重试)

能够看到所选学生的courses 和grades

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvam9obnNvbmJsb2c=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">

2.更新Create 页面

打开Controllers\StudentController.cs,使用以下的代码替换HttpPost Create方法

[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)
{
//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);
}

上面的代码将ASP.NET MVC模型绑定器创建的Student实体加入到Students 实体集并保存到数据库中。

(模型绑定器能够让你更easy的提交表单数据,能够将提交的表单值转换为CLR值并将它们作为參数传递给Controller中的方法。在本项目中,模型绑定器使用了表单集合中的属性值实例化了一个Student
实体)

这里删除了Bind 属性中的ID參数。由于ID是primary key。SQL Server在插入数据时会自己主动设置该值。

安全注意:ValidateAntiForgeryToken属性有助于防止跨站请求伪造(cross-site request forgery)攻击。可是须要在视图中设置对应的Html.AntiForgeryToken()语句。

Bind属性能够防止过份提交(over-posting)。举例来说,如果Student实体中包括一个Secret 字段,你不希望在Web页面中更新它

 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; } public virtual ICollection<Enrollment> Enrollments { get; set; }
}

即使在Web页面中没有Secret字段,黑客也能够通过工具比如Fiddler或者JavaScript 将表单数据包含Secret值提交到server。假设不使用Bind属性来限制模型绑器须要的字段。模型绑定器会将接收到的Secret值更新至数据库中。以下的截图是通过Fiddler工具来提交表单数据

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvam9obnNvbmJsb2c=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">

OverPost值将会被成功的更新至数据库,这是你不希望看到的。

为了安全起见,最好使用Bind属性的Include參数,也能够使用Exclude參数排除那些你不想要更新的属性。

可是这里推荐使用Include,由于假设你在实体中加入了一个新的属性,Exclude并不会将这个新加入的属性排除在外。

还有一种替代方法是在模型绑定时使用视图模型,视图模型中仅仅包括你想要绑定的属性。

除了Bind属性。上面的代码中仅仅须要增加try-catch块,假设在保存更改时引发DataException异常,就会在页面中显示对应的错误信息。

DataException异常有时是由外部事件引发而不是由于程序错误,所以建议用户重试。记住在生产环境下,全部的应用程序错误都应该被记录下来。

Views\Student\Create.cshtml中的代码和Details.cshtml中的非常相似,除了DisplayFor被EditorFor和ValidationMessageFor帮助器替代

<div class="form-group">
@Html.LabelFor(model => model.LastName, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.LastName)
@Html.ValidationMessageFor(model => model.LastName)
</div>
</div>

Create.cshtml也包括了@Html.AntiForgeryToken()方法以防止跨站请求伪造攻击。

执行项目,选择Students选项卡。并点击Create
New

输入姓名和无效的日期,然后单击Create查看错误消息

默认情况下使用的是server端验证,以后会教大家通过加入属性来生成client验证,以下的代码展示了Create 方法中的模型验证检查

if (ModelState.IsValid)
{
db.Students.Add(student);
db.SaveChanges();
return RedirectToAction("Index");
}

改动日期为一个有效的值,点击Create,能够看到新加入的Student信息

3.更新Edit HttpPost页面

在Controllers\StudentController.cs中,HttpGet Edit方法(没有使用HttpPost属性的那一个)和Details方法一样使用Find方法来检索所选择的Student实体。

使用以下的代码替换HttpPost Edit方法:

        [HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "ID,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 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方法。但不同的是这里在实体中设置了一个标志位来指明它已经被更改,而不是将由模型绑定器创建的实体加入到实体集。

当调用SaveChanges方法时,Modified标志会导致 Entity Framework创建SQL语句并更新数据库。数据库中该行的全部列都将被更新,包含那些用户并没有更改的,并忽略并发冲突。

实体状态、附加和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来更新数据库。

DbContext 在读取一个实体并将其呈现到页面上后就会被销毁。当HttpPost
Edit方法被调用。此时会生成一个新的请求和DbContext 实例,所以你必须手动设置实体状态为Modified。然后当你调用SaveChanges方法时,Entity Framework 会更新数据库行的全部列,由于数据库上下文没有办法知道你究竟更改了哪些属性。

假设你希望SQL Update语句仅仅更新那些用户实际更改的字段,你能够先将原来的值以某种方法(比方隐藏字段)保存起来,这样在调用HttpPost Edit方法时就能够使用它们,然后你能够使用原来的值来创建一个Student实体,调用Attach方法。并使用新的值更新该实体,最后调用SaveChanges方法。

Views\Student\Edit.cshtml 中的HTML 和Razor代码与Create.cshtml中的非常类似。

执行项目。选择Students选项卡,点击当中一个学生的Edit链接

改动当中的值,点击Save。能够在Index页面中看到已经改动过的数据

4.更新Delete页面

在Controllers\StudentController.cs中,由模板生成的HttpGet Delete方法使用Find方法检索所选的Student实体。

然而,当调用SaveChanges方法失败时为了显示自己定义的错误信息。你须要向该方法和相相应的视图中加入一些功能。

就像update和create操作,delete操作也须要两个动作方法。用于响应Get请求的方法用来显示一个能够让用户<批准或取消delete操作的视图。假设用确认运行delete操作,此时会产生一条POST请求。并调用HttpPost Delete方法,该方法运行真正的delete操作。

在HttpPost Delete方法中加入try-catch块能够用来捕获数据库更新时可能出现的不论什么错误,假设出现了错误。则HttpPost Delete方法会调用HttpGet Delete方法。并向其传递一个參数指明发生了错误,然后HttpGet Delete会显示一个错误信息,并给用户一个取消或重试的机会。

使用以下的代码替换HttpGet Delete方法:

public ActionResult Delete(int? id, bool? saveChangesError=false)
{
if (id == null)
{
return new HttpStatusCodeResult(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);
}

上面的代码接受一个可选择參数,指明该方法在保存更改出现错误后是否被调用。

当HttpGet Delete方法不是因为出现错误而被调用的话。该參数值为false。当HttpPost Delete出现了错误而调用HttpGet Delete方法时该參数为true并在对应的视图上显示错误信息。

使用以下的代码替换HttpPost Delete方法(名称为DeleteConfirmed的那个)。此方法用来运行真正的delete操作并捕获不论什么数据库更新错误

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Delete(int id)
{
try
{
Student student = db.Students.Find(id);
db.Students.Remove(student);
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");
}

上面的代码从数据库中检索要删除的实体。然后调用Remove方法将实体的状态设置为Deleted,最后调用SaveChanges方法并生成一条SQL Delete命令。另外你也能够将方法名DeleteConfirmed改为Delete。框架代码将HttpPost Delete方法命名为DeleteConfirmed是为了为其设置一个独一无二的名称(CLR重载方法须要有不同的參数)。如今遵守MVC的约定。HttpPost和HttpGet delete方法使用了同样的名字,并为它们设置不同的參数。

假设你想提高高訪问量应用程序的性能,你要避免使用不必要的SQL查询。

使用以下的代码替换Find和Remove方法

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

上面的代码使用唯一的主键值实例化了一个学生实体并设置实体状态为Deleted。这便是Entity Framework为了删除一个实体所须要做的动作。

如前所述HttpGet Delete方法并不会运行数据删除操作,在一个Get请求响应中运行delete操作(运行不论什么edit操作、create操作或者其他对数据进行更改的操作)将带来安全风险。

在Views\Student\Delete.cshtml中加入错误信息

<h2>Delete</h2>
<p class="error">@ViewBag.ErrorMessage</p>
<h3>Are you sure you want to delete this? </h3>

执行项目,点击Students选项卡,点击当中一个学生的Delete链接:

点击Delete。你会看到在Index页面中该学生已经被删除。

5.确保数据库连接适时关闭

要确保数据库连接被正确的关闭并释放所占用的资源,在你使用完数据库上下文时,必需要将其销毁。这就是为什么框架代码在StudentController.cs的最后部分提供了一个Dispose方法

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

Controller类实现了IDisposeable接口,所以上面的代码通过重写Dispose(bool)方法来显式的销毁数据库上下文实例。

6.处理事务

默认情况下,Entity Framework隐式的实现事务处理。当你对多行或者多个表进行更改后调用SaveChanges方法,Entity Framework会自己主动确保所有更改要么所有成功要么所有失败。假设已经做完一些更改后发生了一个错误。那么所有的更改包含已做完的都将自己主动回滚。

欢迎转载。请注明文章出处:http://blog.csdn.net/johnsonblog/article/details/38711659

博客搬家啦。我的小站:MVC5 Entity Framework学习(2):实现主要的CRUD功能

还大家一个健康的网络环境。从你我做起

项目源代码:https://github.com/johnsonz/MvcContosoUniversity

THE END

MVC5 Entity Framework学习之实现主要的CRUD功能的更多相关文章

  1. MVC5 Entity Framework学习

    MVC5 Entity Framework学习(1):创建Entity Framework数据模型 MVC5 Entity Framework学习(2):实现基本的CRUD功能 MVC5 Entity ...

  2. MVC5 Entity Framework学习之创建复杂的数据模型

    目录(?)[-] 使用属性来自定义数据模型 DataType属性 StringLength属性 Column 属性 完成对Student实体的更改 Required 属性 Display 属性 Ful ...

  3. MVC5 Entity Framework学习之Entity Framework高级功能(转)

    在之前的文章中,你已经学习了如何实现每个层次结构一个表继承.本节中你将学习使用Entity Framework Code First来开发ASP.NET web应用程序时可以利用的高级功能. 在本节中 ...

  4. MVC5 Entity Framework学习之Entity Framework高级功能

    在之前的文章中,你已经学习了怎样实现每一个层次结构一个表继承. 本节中你将学习使用Entity Framework Code First来开发ASP.NET web应用程序时能够利用的高级功能. 在本 ...

  5. MVC5 Entity Framework学习之实现继承

    之前你已经学习了怎样处理并发异常,在本节中你将学习怎样实现继承. 在面向对象的编程中,你能够使用继承来重用代码.接下来你将改动Instructor和Student类,让它们派生自Person基类,该基 ...

  6. MVC5 Entity Framework学习参加排序、筛选和排序功能

    上一篇文章实现Student 基本的实体CRUD操作.本文将展示如何Students Index页添加排序.筛选和分页功能. 以下是排序完成时.经过筛选和分页功能截图,您可以在列标题点击排序. 1.为 ...

  7. Entity Framework 学习整理(分播客整理)

    MSDN: http://msdn.microsoft.com/en-us/data/aa937723 台湾博客: http://www.dotblogs.com.tw/yc421206/ http: ...

  8. Entity Framework 学习整理

    MSDN: http://msdn.microsoft.com/en-us/data/aa937723 台湾博客: http://www.dotblogs.com.tw/yc421206/ http: ...

  9. Entity Framework 学习笔记(2)

    上期回顾:Entity Framework 学习笔记(1) Entity Framework最主要的东西,就是自己创建的.继承于DbContext的类: /// <summary> /// ...

随机推荐

  1. 关于Linux系统指令 top 之 %si 占用高,分析实例一

    续“top %wa 高的问题”之后,又遇到top之%si过高(高峰时段超过95%)的问题. %wa高,说明磁盘忙.譬如磁盘读写次数非常高.%si高,是否说明软中断忙?是否也说明软中断次数非常高呢? 为 ...

  2. SpringMVC深度探险(二) —— SpringMVC概览

    对于任何事物的研究,总是由表及里.由浅入深地进行.在本系列的第二篇文章中,我们将通过不同的观察视角,对SpringMVC做一些概要性的分析,帮助大家了解SpringMVC的基本构成要素.SpringM ...

  3. R matrix 转换为 dataframe

    When I try converting a matrix to a data frame, it works for me: > x <- matrix(1:6,ncol=2,dimn ...

  4. Qt封装QTcpServer参考资料--QTcpServer多线程实现

    目的:每个客户端连接的tcpSocket分别分配一个专门的线程来处理. 实现时分别继承QTcpServer和QTcpScoket实现出自己需要的类. 继承QTcpServer为每个客户端连接时分配线程 ...

  5. 关于Unity的开发思路

    我现在的思路大概是这样的,2D游戏 Hierachy视图 有一个总的Canvas节点,挂载一个总的游戏控制脚本game_scene,下面有这样一些子节点 1.game_root:下面存放游戏界面中的物 ...

  6. IDEA中maven项目导jar包太慢

    版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/PROGRAM_anywhere/article/details/53842058参考了网上的一些教程 ...

  7. C++ 指针二维数组, C++二维指针数组笔记

    C++ 二维动态数组 一. 已知第一维 #include <iostream> using namespace std; int main(int argc, char const *ar ...

  8. 7款开源ERP系统比较

    [网络转载] 现在有许多企业将ERP项目,在企 业中没有实施好,都归咎于软件产品不好.其实,这只是你们的借口.若想要将ERP软件真正与企业融合一体,首先得考虑企业的自身情况,再去选择适合的 ERP软件 ...

  9. SAP,Oracle和国产系统的比较心得

      以下这个心得感同身受,小生如今好歹也做过十几家企业,包括民企,中大型外企,国企的项目, 都经历了TMD的从金蝶用友切换到Oracle, SAP 每当上线的时候 总有用户跳出来比较说 SAP,Ora ...

  10. 计算机网络——网络层

    一.虚拟线路与数据报线路 1. 在网络层提供有连接的计算机网络为虚电路网络: 如因特网:它需要VC(虚拟电路)号,用于建立虚拟电路的报文称为信令报文,相关的协议称为信令协议: 无连接的网络为数据报网络 ...