• Contoso 大学


    原文地址: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 表格中显示这个集合的内容。

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

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

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

    打开 ViewsStudentDetails.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 建立创建页面

    ControllersStudentController.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 的异常被抛出,错误信息将会被显示出来,这类错误典型的是由于一些内部错误,而不是程序错误,所以建议用户再重新试一次。在 ViewsStudentCreate.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 创建一个编辑页面

    ControllersStudentController.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

    ViewsStudentEdit.cshtml中的代码类似于 Create.cshtml ,不需要修改。

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

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

    2-4 创建删除页面

    ControllersStudentController.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 实体,仅仅设置了主键的值,然后将实体的状态设置为 DeletedEF 删除实体仅仅需要这些信息。

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

    ViewsStudentDelete.cshtml 中,在 h2 h3 之间增加下面的代码:

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

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

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

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

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

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

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

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

  • 相关阅读:
    C++面向对象三大特性
    4G通信技术LTE介绍
    汉澳战斗檄文,跟着汉澳去战斗
    AdapterView及其子类之二:使用ListActivity及ArrayAdapter创建列表
    [置顶] Objective-C ,ios,iphone开发基础:protocol 协议(委托,代理)的声明
    C语言中几种类型所占字节数
    UART, SPI, IIC的详解及三者的区别和联系
    数学基础详解 2——概率论与数理统计
    1—机器学习简介
    Python基础(11)——反射、异常处理
  • 原文地址:https://www.cnblogs.com/itjeff/p/4140702.html
Copyright © 2020-2023  润新知