EntityFramework_MVC4中EF5 新手入门教程之二 ---2.执行基本的 CRUD 功能

在前面的教程中,您创建 MVC 应用程序中,存储和显示数据使用实体框架和 SQL 服务器 LocalDB。在本教程中,您会审查和自定义的 CRUD (创建、 读取、 更新、 删除) MVC 脚手架会自动为您在控制器和视图中创建的代码。

它是常见的做法,实施资源库模式,以创建您的控制器和数据访问层之间的一个抽象层。为了保持这些教程简单,不会直到后来在这个系列教程执行一个存储库。

在本教程中,您将创建以下 web 页:

Student_Details_page

Student_Edit_page

Student_Create_page

 

Student_delete_page

创建详细信息页

学生Index 页的搭建的代码排除在外的Enrollments属性,因为该属性包含一个集合。Details 页中,您将显示在 HTML 表中的集合的内容。

Controllers\StudentController.csDetails视图的操作方法使用 Find方法检索单个Student实体。

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

注册表项值将传递给作为id参数方法和索引页上来自路网数据的Details的超链接。

  1. 打开Views\Student\Details.cshtml每个字段显示使用的DisplayFor的帮手,如下面的示例所示:
        <div class="display-label">
             @Html.DisplayNameFor(model => model.LastName)
        </div>
        <div class="display-field">
            @Html.DisplayFor(model => model.LastName)
        </div>
  2. 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>
    </fieldset>
    <p>
        @Html.ActionLink("Edit", "Edit", new { id=Model.StudentID }) |
        @Html.ActionLink("Back to List", "Index")
    </p>

    此代码将遍历中Enrollments导航属性的实体。对于每个Enrollment实体的属性中,它显示课程标题和品位。从存储在Enrollments实体的Course导航属性的Course实体检索课程标题。所有这些数据是从数据库中检索自动当需要它时。(也就是说,您正在使用延迟加载在这里。您没有指定预先加载Courses导航属性,所以第一次尝试访问该属性,查询被发送到数据库以检索数据。你可以阅读更多关于延迟加载和读取相关数据教程稍后在本系列中的预先加载。)

  3. 通过选择Students选项卡并单击一个Details链接为Alexander Carson运行该页。您为所选的学生看到课程和成绩的列表:

    Student_Details_page

更新创建页

  1. Controllers\StudentController.cs,用下面的代码将try-catch块和绑定属性添加到搭建方法替换HttpPostCreate操作方法:
    [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 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);
    }

    此代码将添加创建的 ASP.NET MVC 模型联编程序对Students主体性的Student实体集,然后将更改保存到数据库。(指容易你一起提交的表单数据的 ASP.NET MVC 功能的模型联编程序; 模型联编程序将已发布的窗体值转换为 CLR 类型,并将它们传递给操作方法的参数。在这种情况下,模型联编程序实例化一个Student实体为您使用的Form集合中的属性值。)

    ValidateAntiForgeryToken属性有助于防止跨站点请求伪造攻击。

    安全说明:Bind属性添加保护免受过度过帐例如,假设Student实体包括你不想要此 web 页后,可以更新的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字段在 web 页上,黑客可以使用一种工具如fiddler ,或者写一些 JavaScript,发布一个Secret窗体值。没有限制模型联编程序时它会创建一个Student实例,所使用的字段的绑定属性模型联编程序会拿起那Secret窗体值,用它来更新Student实体实例。然后无论价值黑客指定为Secret窗体字段将更新您的数据库中。下图显示了fiddler 工具 (与值"OverPost") 的Secret 字段中添加到已发布的窗体值。


    "OverPost"将然后将成功添加到 Secret属性插入行,虽然您从来没有打算 web 页将无法更新该属性的值。

    它是安全的最佳做法,Include参数用白名单中的字段的Bind属性。它也是可以到您想要排除的黑名单字段使用Exclude参数。Include是更安全的原因是当你向实体中添加新的属性,新的领域不会自动保护的Exclude列表。

    另一种替代方法,其中一个首选的许多人来说,是只查看模型使用模型绑定。视图模型包含只有您要绑定的属性。一旦完成了 MVC 模型联编程序,您将查看模型属性复制到的实体实例。

    除了在Bind属性try-catch块是搭建代码对您已经的更改。如果所做的更改在保存时,会捕获异常从DataException派生,显示一般错误消息。DataException异常有时是由外部的应用程序,而不是一个编程错误,某个东西导致的所以建议用户再试一次。虽然在此示例中未实施,生产高质量的应用会记录日志记录机制如ELMAH异常 (和非 null 内部异常).

    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.chstml还包括@Html.AntiForgeryToken(),其作品中的控制器有助于防止跨站点请求伪造攻击的ValidateAntiForgeryToken属性。

    不需要更改在Create.cshtml

  2. 运行该页面选择学生选项卡,然后单击Create.

    Student_Create_page

    一些数据验证的工作原理,默认情况。输入名称和无效的日期并单击 Create以查看错误消息。

    Students_Create_page_error_message

    以下突出显示的代码演示模型验证检查。

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

    把日期改到一个有效的值,如 2005/9/1 并单击创建要看见出现在index页中的新学生。

    Students_Index_page_with_new_student

更新编辑文章页面

Controllers\StudentController.csHttpGetEdit方法 (没有HttpPost属性的一个) 使用Find方法检索所选的Student实体,正如您看到的Details方法。您不需要更改此方法。

但是,使用以下代码,以添加try-catch块和绑定属性替换HttpPost Edit操作方法:

[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方法中看到。然而,而不是添加创建的模型联编程序到实体集的实体,此代码指示已更改的实体上设置一个标志。当调用SaveChanges方法时,修改国旗导致实体框架来创建 SQL 语句要更新的数据库行。将更新的数据库行的所有列,其中包括用户没有更改,并且忽略并发冲突。(您将了解如何处理并发稍后在本系列教程中)。

实体状态和附加 SaveChanges 方法

跟踪的数据库上下文是否在内存中的实体与它们相应的行在数据库中,同步此信息确定调用SaveChanges方法时,会发生什么。例如,当你传递给Add方法的一个新的实体,该实体的状态将设置为Added然后当你调用SaveChanges方法,数据库上下文发出 SQLINSERT命令。

实体可以处于以下状态之一:

  • Added该实体不存在尚未在数据库中。SaveChanges方法必须发出一个INSERT语句。
  • Unchanged什么都不需要去做与此实体SaveChanges法。当您从数据库中读取一个实体时,该实体开始与这种地位。
  • Modified某些或所有实体的属性值已被都修改。SaveChanges方法必须发出一个UPDATE语句。
  • Deleted该实体已标记为删除。SaveChanges方法必须发出DELETE语句。
  • Detached该实体不被跟踪的数据库上下文。

在桌面应用程序中,状态变化通常会自动设置。在桌面应用程序的类型中,您阅读的实体,并对一些其属性值进行更改。这将导致其实体的状态自动更改为Modified然后当你调用SaveChanges,实体框架生成更新仅有实际的属性更改 SQLUPDATE语句。

Web 应用程序的已断开连接的性质不允许为此连续的序列。读取实体的DbContext被处置后呈现的页面。当调用HttpPost Edit操作方法、 发送新的请求和你有DbContext的一个新实例时,所以你必须手动将实体状态设置为Modified.然后当您调用SaveChanges,实体框架更新的数据库行的所有列,因为该上下文有没有办法知道哪些属性更改。

如果您想要更新用户实际更改的字段的 SQLUpdate语句,可以以某种方式 (如隐藏字段) 保存的原始值,以便调用HttpPost Edit方法时,他们是可用。然后可以Student使用创建实体的原始值,调用Attach方法与该实体的原始版本,更新到新的值,该实体的值,然后调用SaveChanges.更多的信息,请参阅实体状态和 SaveChanges和 MSDN 数据开发人员中心中的本地数据

Views\Student\Edit.cshtml中的代码类似于你看到在Create.cshtml,并没有修改的必要。

通过选择学生选项卡,然后单击Edit超链接运行该页。

Student_Edit_page

改变的一些数据,单击保存你看到在索引页中更改的数据。

Students_Index_page_after_edit

更新删除页

Controllers\StudentController.csHttpGetDelete方法的模板代码使用Find方法检索所选的Student实体,正如您看到的DetailsEdit的方法。然而,若要实现自定义错误消息到SaveChanges调用失败时,您将向此方法和其相应的视图添加一些功能。

当你看到更新,并创建操作,删除操作需要两个操作方法。调用 GET 请求的响应的方法显示一个视图,使用户有机会批准或取消删除操作。如果用户批准它,则会创建一个 POST 请求。当发生这种情况时, HttpPost Delete方法称为,然后该方法实际上执行删除操作。

你会将try-catch块添加到HttpPost Delete方法,以处理对数据库进行更新时可能会出现的任何错误。如果发生错误,该HttpPost Delete方法调用HttpGet Delete方法,传递给它的参数,指示发生了错误。HttpGet Delete方法然后重新显示确认页和错误消息,使用户有机会取消或再试一次。

  1. 用下面的代码管理错误报告取代HttpGetDelete操作方法:
    public ActionResult Delete(bool? saveChangesError=false, int id = 0)
    {
        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方法调用时,参数为true和一条错误消息传递给视图。

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

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Delete(int id)
    {
        try
        {
            Student student = db.Students.Find(id);
            db.Students.Remove(student);
            db.SaveChanges();
        }
        catch (DataException/* dex */)
        {
            // uncomment dex and log error. 
            return RedirectToAction("Delete", new { id = id, saveChangesError = true });
        }
        return RedirectToAction("Index");
    }

    此代码检索所选的实体,然后调用Remove方法,将该实体的状态设置为Deleted当调用SaveChanges时,生成 SQLDELETE的命令。Delete,也有从DeleteConfirmed 改变操作方法的名称。搭建的代码命名为DeleteConfirmed ,给出了HttpPost方法独特的签名HttpPost Delete方法。(CLR 需要有不同的方法参数的重载的方法)。现在,签名是独一无二的你可以坚持使用 MVC 公约并使用HttpPost相同的名称,HttpGet删除方法。

    如果在一个高容量应用程序提高性能是一个优先事项,你可以避免不必要的 SQL 查询,以检索行通过替换调用FindRemove的方法,用下面的代码,黄色突出显示框所示的代码行:

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

    此代码实例化一个Student实体使用唯一主键值,然后将实体状态设置为Deleted这是所有实体框架所需删除的实体。

    正如所指出,HttpGet Delete方法不会删除数据。执行删除操作响应 GET 请求 (或对于那件事,执行任何编辑操作,创建操作或更改数据的任何其他操作) 会产生安全风险。有关更多信息,请参见ASP.NET MVC 提示 #46 — — 不要使用删除链接,因为它们创建安全漏洞Stephen 瓦尔特的博客上。

  3. Views\Student\Delete.cshtml,添加一条错误消息之间的h2标题和h3标题中,如下面的示例所示:

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

    运行页上选择学生选项卡,然后单击删除超链接:

    Student_Delete_page

  4. 单击删除没有删除学生显示索引页。(您将看到的错误处理代码在处理并发教程稍后在这一系列行动中的示例。)

确保该数据库的连接是否关闭了

为了确保正确关闭数据库连接和他们持有腾出的资源,你应该看到它释放上下文实例。这就是为什么搭建的代码提供Dispose方法末尾的StudentController类中StudentController.cs,如下面的示例所示:

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

Controller基类已经实现IDisposable接口,所以这段代码只是添加重写Dispose(bool)方法来显式处理上下文实例。

摘要

你现在有一套完整的执行简单的 CRUD 操作,为Student实体的页面。MVC 佣工用于生成数据字段的 UI 元素。MVC 佣工有关的详细信息,请参见呈现的窗体使用 HTML 帮助器(页面是 MVC 3 但仍然相关的 MVC 4)。

在接下来的教程中会展开索引页的功能,通过添加排序和分页。

posted @ 2015-01-29 10:48  178mz  阅读(894)  评论(0编辑  收藏  举报