Contoso 大学 - 6 – 更新关联数据

原文地址:http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/updating-related-data-with-the-entity-framework-in-an-asp-net-mvc-application

全文目录:Contoso 大学 - 使用 EF Code First 创建 MVC 应用

在上一次的课程中,你已经学习了如何显示关联的数据,我们将要更新关联的数据。大多数情况下,可能就是更新表的外键字段。对于多对多的关系来说,由于 EF 并没有直接将表与表之间的连接关系暴露出来,你就必须通过显式对相关的导航属性进行添加或者删除实体来完成。

下面的截图展示了我们马上要完成的工作。

6-1  定制课程的创建和编辑页面

当新的课程实体创建的时候,必须包含相关的 Department。为了达到这个目的,脚手架创建的代码,包括创建和编辑的控制器以及视图,都包含了对 Department 的下拉列表的支持。下拉列表设置 Course.DepartmentId 外键属性,这对于 EF 通过 Department 导航属性来加载 Department 实体来说是必须的。下面将要对脚手架生成的代码进行一些小的改动,增加错误的处理以及对列表内容进行排序。

打开 CourseController.cs, 删除原来的 Edit 和 Create 方法,使用下面的代码替换它们。

public ActionResult Create()
{
    PopulateDepartmentsDropDownList();
    return View();
}

[HttpPost]
public ActionResult Create(Course course)
{
    try
    {
        if (ModelState.IsValid)
        {
            db.Courses.Add(course);
            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.");
    }
    PopulateDepartmentsDropDownList(course.DepartmentID);
    return View(course);
}

public ActionResult Edit(int id)
{
    Course course = db.Courses.Find(id);
    PopulateDepartmentsDropDownList(course.DepartmentID);
    return View(course);
}

[HttpPost]
public ActionResult Edit(Course course)
{
    try
    {
        if (ModelState.IsValid)
        {
            db.Entry(course).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.");
    }
    PopulateDepartmentsDropDownList(course.DepartmentID);
    return View(course);
}

private void PopulateDepartmentsDropDownList(object selectedDepartment = null)
{
    var departmentsQuery = from d in db.Departments
                           orderby d.Name
                           select d;
    ViewBag.DepartmentID = new SelectList(departmentsQuery, "DepartmentID", "Name", selectedDepartment);
}

PopulateDepartmentsDropDownList 方法获取经过对 Name 进行排序的 Department,然后创建用于下拉列表的 SelectList 集合,通过 ViewBag 传递到视图中。这个方法包含一个参数,允许调用方可选地传递一个初始选中项目的值。

HttpGet Create 方法调用没有设置选中项的 PopulateDepartmentsDropDownList 方法,因为对于新创建的课程来说,还没有确定归属的系。

public ActionResult Create()
{
    PopulateDepartmentsDropDownList();
    return View();
}

HttpGet Edit 方法则设置了当前选中的项目,基于当前被编辑课程的 DepartmentId。

public ActionResult Edit(int id)
{
    Course course = db.Courses.Find(id);
    PopulateDepartmentsDropDownList(course.DepartmentID);
    return View(course);
}

对于 Create 和 Edit 的 HttpPost 方法来说,在错误信息处理之后,都包含了设置当前选中项目的代码。

 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.");
}
PopulateDepartmentsDropDownList(course.DepartmentID);
return View(course);

代码用来保证即使在显示错误的页面上,也会保持选中的项目。

Views\Course\Create.cshtml, 在 Title 之前增加一个新的字段,允许用户输入课程编号。想之前演示的那样,脚手架没有生成主键字段,因为主键字段对用户没有意义。所以需要添加以便用户能够输入这个值。

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

运行 Create 页面 ( 在课程的 Index 页面,点击 Create New  ),然后输入新的课程。

点击 Create,在 Index 中应该可以看到包含新创建课程的列表。系的名称通过导航属性获取到,显示的数据是正确的。

运行编辑页面 ( 在课程的 Index 页面中在某个课程上选择 Edit )

修改一些数据,然后点击 Save,可以看到更新之后的课程数据。

6-2  增加教师的编辑页面

在修改教师信息的时候,我们希望也能够修改教师的办公室分配。教师实体 Instructor 和办公室分配 OfficeAssignment存在一对一或者一对零的关系,这意味着你必须处理如下的状态:

  • 如果教师原来存在一个办公室分配,但是用户删除了它,那么,你必须移除并且删除这个办公室分配 OfficeAssignment 实体。
  • 如果教师原来没有办公室分配,但是用户输入了一个,你必须创建一个新的办公室分配。
  • 如果用户修改了原来的办公室分配,你必须修改当前的办公分配 OfficeAssignment 实体。

打开 InstructorController.cs,看一下 HttpGet Edit 方法。

public ActionResult Edit(int id)
{
    Instructor instructor = db.Instructors.Find(id);
    ViewBag.InstructorID = new SelectList(db.OfficeAssignments, "InstructorID", "Location", instructor.InstructorID);
    return View(instructor);
}

脚手架生成的代码不是我们希望的,它生成了一个下拉列表,但是我们希望是文本框,将这个方法使用下面的代码替换掉。

public ActionResult Edit(int id)
{
    Instructor instructor = db.Instructors
        .Include(i => i.OfficeAssignment)
        .Include(i => i.Courses)
        .Where(i => i.InstructorID == id)
        .Single();
    return View(instructor);
}

代码中删除了 ViewBag 语句,增加了使用预先加载的相关 OfficeAssignment 和 Course 实体 ( 现在还不需要课程实体,一会就会用到 )。由于 Find 方法不能使用预先加载,所以这里使用 Where 和 Single 方法来获取教师。

将 HttpPost 的 Edit 方法使用下面的代码替换,这里处理了 OfficeAssignment 更新。

[HttpPost]
public ActionResult Edit(int id, FormCollection formCollection)
{
    var instructorToUpdate = db.Instructors
        .Include(i => i.OfficeAssignment)
        .Include(i => i.Courses)
        .Where(i => i.InstructorID == id)
        .Single();
    if (TryUpdateModel(instructorToUpdate, "", null, new string[] { "Courses" }))
    {
        try
        {
            if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment.Location))
            {
                instructorToUpdate.OfficeAssignment = null;
            }

            db.Entry(instructorToUpdate).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();
        }
    }
    return View(instructorToUpdate);
}

代码中处理了如下的内容:

  • 从数据库中获取了教师 Instructor 实体,并且预先加载了相关的的办公室分配 OfficeAssignment 和课程 Course 导航属性。如同在 HttpGet 的 Edit 方法一样。
  • 使用通过模型绑定获取的数据,更新 Instructor 实体,除了课程 Course 导航属性之外。
If (TryUpdateModel(instructorToUpdate, "", null, new string[] { "Courses" }))

( 第二和第三个参数在属性名的前面没有前缀,而且没有被包含的属性列表 ),如果验证失败, TryUpdateModel 方法返回 false,程序将直接转到方法最后的 return View 语句。

  • 如果办公位置为空,设置 Instructor.OfficeAssignment 属性为 null,在 OfficeAssignment 表相关的行将会被删除。
if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment.Location))
{
    instructorToUpdate.OfficeAssignment = null;
}
  • 将修改保存到数据库中。

Views\Instructor\Edit.cshtml, 在 Hire Date 字段的 div 元素之后,增加新的字段来编辑办公位置。

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

运行页面 ( 选中教师 Instructors ,点击某个教师的 Edit 链接 )

修改办公室位置的值,然后保存 Save。

新的办公位置出现在 Index 页面上,打开数据库中的 OfficeAssgnment 表,可以看到表中的数据行。

运行编辑页面,将办公位置 Office Location 清除掉,然后保存 Save。在 Index 页面上办公位置将显示为空白,在表中的行被删除了。

6-3  在教师编辑页面增加课程分配

教师可以教授任意数量的课程。现在你需要扩展教师的编辑页面,增加通过一系列复选框分配可能的能力,如下所示。

在课程 Course 和教师 Instructor 之间存在多对多的关系,这意味着你不需要直接访问表之间的关联。而是通过增加或者删除 Instructor. Course 实体来完成。

在 UI 界面上,与教师相关的课程被显示为一组复选框,在数据库中的每一门课程都有一个对应的复选框,教师当前教授的课程对应的复选框处于被选中状态。用户可以通过选中或者取消选中来改变教师教授的课程。如果课程的数量巨大,你可能需要采取其他的方式来显示这些数据,但是你可以使用类似的方式来控制导航属性以便创建和删除关系。

为了为视图提供复选框数据,你需要使用视图模型 ViewModel,在 ViewModels 文件夹中创建 AssignedCourseData.cs,使用下面的代码替换生成的代码。

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.ViewModels
{
    public class AssignedCourseData
    {
        public int CourseID { get; set; }
        public string Title { get; set; }
        public bool Assigned { get; set; }
    }
}

在 InstructorController.cs 中,找到 HttpGet Edit 方法,调用通过视图模型为视图中复选框提供数据的新方法,如下所示。

public ActionResult Edit(int id)
{
    Instructor instructor = db.Instructors
        .Include(i => i.OfficeAssignment)
        .Include(i => i.Courses)
        .Where(i => i.InstructorID == id)
        .Single();
    PopulateAssignedCourseData(instructor);
    return View(instructor);
}

private void PopulateAssignedCourseData(Instructor instructor)
{
    var allCourses = db.Courses;
    var instructorCourses = new HashSet<int>(instructor.Courses.Select(c => c.CourseID));
    var viewModel = new List<AssignedCourseData>();
    foreach (var course in allCourses)
    {
        viewModel.Add(new AssignedCourseData
        {
            CourseID = course.CourseID,
            Title = course.Title,
            Assigned = instructorCourses.Contains(course.CourseID)
        });
    }
    ViewBag.Courses = viewModel;
}

新创建的方法中,读取所有的课程实体,加载到视图模型中,对于每一个课程,检查这个课程是否存在于教师实体的 Courses 导航集合属性中。为了更加有效地遍历教师讲授的课程,将教师讲授的课程通过 HashSet 集合处理,课程中教师讲授的课程的 Assigned 属性被赋予 true。在视图中通过这个属性来判断复选框是否显示为选中状态。最后,这个集合通过 ViewBag 传递到视图中。

然后,增加当用户点击 Save 后执行的代码。将 HttpPost 中的 Edit 方法使用下面的代码替换掉,这里通过调用一个新的方法将教师 Instructor 的导航属性 Courses 更新。

[HttpPost]
public ActionResult Edit(int id, FormCollection formCollection, string[] selectedCourses)
{
    var instructorToUpdate = db.Instructors
        .Include(i => i.OfficeAssignment)
        .Include(i => i.Courses)
        .Where(i => i.InstructorID == id)
        .Single();
    if (TryUpdateModel(instructorToUpdate, "", null, new string[] { "Courses" }))
    {
        try
        {
            if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment.Location))
            {
                instructorToUpdate.OfficeAssignment = null;
            }

            UpdateInstructorCourses(selectedCourses, instructorToUpdate);

            db.Entry(instructorToUpdate).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.");
       }
    }
    PopulateAssignedCourseData(instructorToUpdate);
    return View(instructorToUpdate);
}

private void UpdateInstructorCourses(string[] selectedCourses, Instructor instructorToUpdate)
{
    if (selectedCourses == null)
    {
        instructorToUpdate.Courses = new List<Course>();
        return;
    }

    var selectedCoursesHS = new HashSet<string>(selectedCourses);
    var instructorCourses = new HashSet<int>
        (instructorToUpdate.Courses.Select(c => c.CourseID));
    foreach (var course in db.Courses)
    {
        if (selectedCoursesHS.Contains(course.CourseID.ToString()))
        {
            if (!instructorCourses.Contains(course.CourseID))
            {
                instructorToUpdate.Courses.Add(course);
            }
        }
        else
        {
            if (instructorCourses.Contains(course.CourseID))
            {
                instructorToUpdate.Courses.Remove(course);
            }
        }
    }
}

如果没有复选框被选中,在 UpdateInstructorCourse 方法中,使用一个空的集合来初始化 Courses 导航属性。

if (selectedCourses == null)
{
    instructorToUpdate.Courses = new List();
    return;
}

然后,代码遍历数据库中所有的课程,如果课程的复选框被选中了,但是没有包含在教师 Insturctor 的 Courses 集合中。这个课程将会被加入到导航属性集合中。

if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
    if (!instructorCourses.Contains(course.CourseID))
    {
        instructorToUpdate.Courses.Add(course);
    }
}

如果课程没有被选中,但是在教师的导航属性 Courses 集合中,就从集合属性中删除掉。

else
{
    if (instructorCourses.Contains(course.CourseID))
    {
        instructorToUpdate.Courses.Remove(course);
    }
}

在 Views\Instructor\Edit.cshtml 中,在 OfficeAssignment 区域的 div 之后,增加一个 Courses 区域,通过一组复选框来显示课程的状态。

<div class="editor-field">
    <table>
        <tr>
            @{
                int cnt = 0;
                List<ContosoUniversity.ViewModels.AssignedCourseData> courses = ViewBag.Courses;

                foreach (var course in courses) {
                    if (cnt++ % 3 == 0) {
                        @:  </tr> <tr>
                    }
                    @: <td>
                        <input type="checkbox"
                               name="selectedCourses"
                               value="@course.CourseID"
                               @(Html.Raw(course.Assigned ? "checked=\"checked\"" : "")) />
                        @course.CourseID @:  @course.Title
                    @:</td>
                }
                @: </tr>
            }
    </table>
</div>

代码创建一个 HTML 的三列表格,每一列中显示课程的标题和编号,后面跟着一个复选框。复选框的名称是相同的 ( “selectedCourses” ),这样在模型绑定的时候就会被连接成一组。复选框的 value 属性设置为 CourseID。当提交页面的时候,选中的复选框代表的 CourseID 将以数组的形式传递给控制器。

在复选框被初始化的时候,选中课程对应的复选框的 checked 属性被设置为选中状态。

在修改了课程状态之后,当回到 Index 页面的时候,需要验证这些修改。因此,需要在页面的表格中增加一列,在这里不需要使用 ViewBag,因为需要的信息已经通过教师实体 Instructor 的导航属性 Courses 传递到页面了。

在 Views\Instuctor\Index.cshtml 中,在 <th>Office</th> 之后,增加一个 <th>Course</th> 的标题列,如下所示。

<tr>
    <th></th>
    <th>Last Name</th>
    <th>First Name</th>
    <th>Hire Date</th>
    <th>Office</th>
    <th>Courses</th>
</tr> 

然后,在办公位置的单元格之后增加一个详细内容的单元格。

<td>
    @{
        foreach (var course in item.Courses)
        {
            @course.CourseID @:  @course.Title <br />
        }
    }
</td>

运行 Instructor 的 Index 页面,检查教师的授课情况。

点击 Edit 查看编辑页面。

修改一些课程的授课情况,然后保存 Save,修改的结果在 Index 页面中可以看到。

你已经完成了修改关联数据的工作。通过目前的课程你已经可以完成增、删、改、查所有的操作,但是还没有考虑并发问题。下一次我们将探讨并发问题,展示处理并发的方式,然后对已经完成的实体的增、删、改、查的代码增加并发处理。

时间: 2024-10-08 16:09:16

Contoso 大学 - 6 – 更新关联数据的相关文章

Contoso 大学 - 5 – 读取关联数据

原文地址:http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/reading-related-data-with-the-entity-framework-in-an-asp-net-mvc-application 全文目录:Contoso 大学 - 使用 EF Code First 创建 MVC 应用 在前面的课程中已经完成了 School 数据模型.在这次的课程中,将要读取和显示相关的数据,这里指的是 EF 通

MVC5+EF6--8 更新关联数据

近期学习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-appli

EF学习笔记(八):更新关联数据

学习笔记主目录链接:ASP.NET MVC5 及 EF6 学习笔记 - (目录整理) 上一篇链接:EF学习笔记(七):读取关联数据 本篇原文链接:Updating Related Data 本篇主要考虑对于有关联的数据进行新增.删除.更新操作:比如Course .Instructor: 对于Course来说,新增时候必须定义属于哪个Department,所以在新增.更新操作的时候,必须要用户选择Department: MVC5在选择基础控制器及视图框架的时候,如果选择EF的操作框架,则会自动带一

EntityFramework_MVC4中EF5 新手入门教程之六 ---6.通过 Entity Framework 更新关联数据

在前面的教程中,您将显示相关的数据 :在本教程中,您会更新相关的数据.对于大多数的关系,这个目标是可以通过更新相应的外键字段来达到的.对于多对多关系,实体框架并不直接,暴露联接表,因此您必须显式添加和删除,并从相应的导航属性的实体. 下面的插图显示页面,您将利用工作. 为课程自定义创建和编辑页面 当创建新的课程实体时,它必须拥有一个关系到现行的部门.为了推动这项工作,搭建的代码包括控制器方法,并且创建和编辑视图中包括用于选择处的下拉列表.下拉列表中设置Course.DepartmentID的外键

[渣译文] 使用 MVC 5 的 EF6 Code First 入门 系列:为ASP.NET MVC应用程序更新相关数据

这是微软官方教程Getting Started with Entity Framework 6 Code First using MVC 5 系列的翻译,这里是第六篇:为ASP.NET MVC应用程序更新相关数据 原文: Updating Related Data with the Entity Framework in an ASP.NET MVC Application 译文版权所有,谢绝全文转载--但您可以在您的网站上添加到该教程的链接. 在之前的教程中您已经成功显示了相关数据.在本教程中

Contoso 大学 - 使用 EF Code First 创建 MVC 应用,实例演练

Contoso 大学 Web 示例应用演示了如何使用 EF 技术创建 ASP.NET MVC 应用.示例中的 Contoso 大学是虚构的.应用包括了类似学生注册.课程创建以及教师分配等功能. 这个系列教程展示了创建 Contoso 大学应用的步骤.你可以 下载完整 的程序,或者按照教程一步一步创建它,这个教程中使用 C# 进行演示,下载的代码中同时包含 C# 和 VB 实现.如果你有与这个教程没有直接相关的问题,可以张贴到 ASP.NET Entity Framework forum  或者

Contoso 大学 - 10 - 高级 EF 应用场景

原文地址:http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/advanced-entity-framework-scenarios-for-an-mvc-web-application全文目录:Contoso 大学 - 使用 EF Code First 创建 MVC 应用 在上一个教程中,你已经实现了仓储和工作单元模式.这个教程涵盖下列主题: 执行原始的 SQL 查询 执行没有跟踪的查询 检查发送到数据库的查询

Contoso 大学 - 7 – 处理并发

原文地址:http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/handling-concurrency-with-the-entity-framework-in-an-asp-net-mvc-application 全文目录:Contoso 大学 - 使用 EF Code First 创建 MVC 应用 在上一次的教程中我们处理了关联数据问题.这个教程演示如何处理并发问题.你将使用 Department 实体创建一

Contoso 大学 - 1 - 为 ASP.NET MVC 应用程序创建 EF 数据模型

原文地址:Creating an Entity Framework Data Model for an ASP.NET MVC Application (1 of 10) Contoso 大学 Web 示例应用演示了如何使用 EF 技术创建 ASP.NET MVC 应用.示例中的 Contoso 大学是虚构的.应用包括了类似学生注册.课程创建以及教师分配等功能. 这个系列教程展示了创建 Contoso 大学应用的步骤.你可以 下载完整 的程序,或者按照教程一步一步创建它,这个教程中使用 C# 进