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

在前面的教程中,您将显示相关的数据 ;在本教程中,您会更新相关的数据。对于大多数的关系,这个目标是可以通过更新相应的外键字段来达到的。对于多对多关系,实体框架并不直接,暴露联接表,因此您必须显式添加和删除,并从相应的导航属性的实体。

下面的插图显示页面,您将利用工作。

Course_create_page

Instructor_edit_page_with_courses

为课程自定义创建和编辑页面

当创建新的课程实体时,它必须拥有一个关系到现行的部门。为了推动这项工作,搭建的代码包括控制器方法,并且创建和编辑视图中包括用于选择处的下拉列表。下拉列表中设置Course.DepartmentID的外键属性,,这是所有实体框架所需加载具有适当的Department实体的Department导航属性。您可以使用搭建的代码,但改变它微微地添加错误处理和下拉列表进行排序。

CourseController.cs,删除四个EditCreate方法并替换为以下代码:

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

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

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

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(
    [Bind(Include = "CourseID,Title,Credits,DepartmentID")]
    Course course)
{
   try
   {
      if (ModelState.IsValid)
      {
         db.Entry(course).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.");
   }
   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方法获取列表按名称排序的所有部门、 创建下拉列表,SelectList的集合并将集合传递到ViewBag属性中查看。该方法接受可选的selectedDepartment参数允许调用代码来指定当呈现下拉列表中将选定的项。视图会将DepartmentID的名称传递给DropDownListhelper,和助手就会知道应该命名为DepartmentIDSelectList ViewBag对象中寻找.

HttpGet Create方法调用PopulateDepartmentsDropDownList方法无需设置所选的项目,因为有一个新课程部尚未确立:

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

HttpGetEdit方法设置所选的项目,基于已分配给课程正在编辑该署的 ID:

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

CreateEditHttpPost方法还包括设置选定的项,当他们出现错误之后重新显示该页的代码:

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

此代码可确保当要显示错误消息,重新显示页上,不管部入选始终保持选定状态。

Views\Course\Create.cshtml,添加突出显示的代码,以创建一个新的课程编号字段在标题字段之前。正如解释在早些时候的教程中,主键字段不搭建在默认情况下,但此主键是有意义的所以您希望用户能够输入的密钥值。

@model ContosoUniversity.Models.Course

@{
    ViewBag.Title = "Create";
}

<h2>Create</h2>

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
    @Html.ValidationSummary(true)

    <fieldset>
        <legend>Course</legend>

        <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>        <div class="editor-label">
            @Html.LabelFor(model => model.Title)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Title)
            @Html.ValidationMessageFor(model => model.Title)
        </div>

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

        <div class="editor-label">
            @Html.LabelFor(model => model.DepartmentID, "Department")
        </div>
        <div class="editor-field">
            @Html.DropDownList("DepartmentID", String.Empty)
            @Html.ValidationMessageFor(model => model.DepartmentID)
        </div>

        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

Views\Course\Edit.cshtml、 Views\Course\Delete.cshtml、 Views\Course\Details.cshtml添加课程数字字段在Title字段之前。因为它是主键,它会显示,但不能更改。

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

运行创建页 (显示路线索引页面,并单击新建) 和输入一门新课程的数据:

Course_create_page

单击创建课程索引页面显示添加到列表中的新课程。在索引页面列表中的部门名称来自于导航属性,显示正确建立了关系。

Course_Index_page_showing_new_course

运行编辑页 (显示路线索引页面,并在课程上单击编辑)。

Course_edit_page

更改页面上的数据并单击保存课程索引页显示已更新的课程数据。

添加Instructors编辑页

当您编辑一个教练记录时,你想要能够更新教师的办公室分配。Instructor实体具有一个到零或一个关联OfficeAssignment实体,这就意味着你必须处理以下情况:

  • 如果用户清除办公室分配,它原本的价值,您必须删除并删除OfficeAssignment实体。
  • 如果用户输入的办公室分配值,它最初为空,您必须创建一个新的OfficeAssignment实体。
  • 如果用户更改办公室被分配的值,则必须更改现有的OfficeAssignment实体中的值。

打开InstructorController.cs ,然后看看HttpGetEdit方法:

public ActionResult Edit(int id = 0)
{
    Instructor instructor = db.Instructors.Find(id);
    if (instructor == null)
    {
        return HttpNotFound();
    }
    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)
        .Where(i => i.InstructorID == id)
        .Single();
    return View(instructor);
}

这段代码滴ViewBag声明,并添加预先加载关联的OfficeAssignment实体。所以相反的WhereSingle方法用于选择教练的情况下,不能使用Find方法,执行预先加载。

用以下代码替换HttpPost Edit方法。处理办公室工作分配更新:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(int id, FormCollection formCollection)
{
   var instructorToUpdate = db.Instructors
       .Include(i => i.OfficeAssignment)
       .Where(i => i.InstructorID == id)
       .Single();

   if (TryUpdateModel(instructorToUpdate, "",
      new string[] { "LastName", "FirstMidName", "HireDate", "OfficeAssignment" }))
   {
      try
      {
         if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment.Location))
         {
            instructorToUpdate.OfficeAssignment = null;
         }

         db.Entry(instructorToUpdate).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.");
      }
   }
   ViewBag.InstructorID = new SelectList(db.OfficeAssignments, "InstructorID", "Location", id);
   return View(instructorToUpdate);
}

该代码执行以下任务:

  • 获取从数据库使用的当前的Instructor实体预先加载OfficeAssignment导航属性。这是你的所作所为中HttpGetEdit方法相同。

  • 从模型联编程序的值更新检索到的Instructor实体。您想要包括的属性使用的TryUpdateModel重载使您到白名单中这可以防止过度张贴,如所述第二个教程.

       if (TryUpdateModel(instructorToUpdate, "",
          new string[] { "LastName", "FirstMidName", "HireDate", "OfficeAssignment" }))
  • 如果办公室位置为空,将设置Instructor.OfficeAssignment属性,以便将删除OfficeAssignment表中的相关的行,则为 null。

    if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment.Location))
    {
        instructorToUpdate.OfficeAssignment = null;
    }
  • 将更改保存到数据库中。

Views\Instructor\Edit.cshtmldiv元素为雇佣日期字段中,, 添加新的字段编辑办公地点:

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

运行该页面 (选择教官选项卡,然后点击编辑讲师)。更改办公地点并单击保存.

Changing_the_office_location

在指导老师编辑页添加课程作业

教练可以教任何数目的课程。现在您将通过添加更改课程作业使用一组复选框,如下面的屏幕快照中所示的能力增强导师编辑页面:

Instructor_edit_page_with_courses

CourseInstructor实体之间的关系是多,这意味着您没有直接访问到联接表。相反,您将添加和删除实体,从Instructor.Courses导航属性。

用户界面中,使您能够更改哪些课程的讲师是分配给是一组复选框。显示数据库中的每一门课程的复选框,并选择了那些讲师当前分配给。用户可以选择或清除复选框以更改课程作业。如果课程数目大得多,你可能想要使用不同的方法,在视图中,显示数据,但您将使用相同的方法操纵导航属性以创建或删除关系。

为列表中的复选框提供数据到视图,您将使用一个视图模型类。Viewmodel文件夹中创建AssignedCourseData.cs和现有的代码替换为以下代码:

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

InstructorController.cs,用下面的代码替换HttpGetEdit方法。高亮行为所做的更改。

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导航属性,调用新的PopulateAssignedCourseData 方法,以便为使用AssignedCourseData 视图模型类的复选框数组提供的信息。

PopulateAssignedCourseData 方法中的代码读取通过所有Course实体以加载列表,使用视图模型类的课程。对于每个课程,该代码检查教师的Courses导航属性中是否存在该课程。若要创建高效的查找,检查是否课程指派给指导员时,分配给教师的课程都放入HashSet集合。Assigned属性设置为true ,课程教师分配。查看将使用此属性来确定哪些复选框必须显示为选中。最后,列表被传递给一个ViewBag属性中的视图。

接下来,添加用户单击保存时执行的代码。HttpPost Edit方法替换以下代码中,调用更新Instructor实体的Courses导航属性的新方法。突出显示所做的更改。

[HttpPost]
[ValidateAntiForgeryToken]
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, "", 
      new string[] { "LastName", "FirstMidName", "HireDate", "OfficeAssignment" }))
   {
      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 /* 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.");
      }
   }
   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);
         }
      }
   }
}

由于视图没有Course实体的集合,模型联编程序不能自动更新Courses 导航属性。而不是使用模型联编程序更新课程导航属性,你可以做到在新的UpdateInstructorCourses方法。因此,您需要将Courses属性排除模型绑定。这并不需要对调用TryUpdateModel ,因为您正在使用白名单过载和Courses不在包含列表中的代码的任何更改。

如果没有复选框被选中,在UpdateInstructorCourses代码初始化具有一个空集合的Courses导航属性:

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

代码遍历所有的课程,在数据库中,然后检查每个课程反对那些当前分配给与之相对的在视图中选择的教练。为方便高效的查找,对后者的两个集合存储在HashSet对象。

如果为一门课程的复选框,但本课程并不是在Instructor.Courses导航属性,该课程添加到中的导航属性的集合。

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

如果一门课程对应的复选框没有选中,但课程是在Instructor.Courses导航属性,课程是从导航属性中删除。

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

Views\Instructor\Edit.cshtml,通过添加下面突出显示的代码为OfficeAssignment字段div元素后立即添加课程领域与数组的复选框:

@model ContosoUniversity.Models.Instructor

@{
    ViewBag.Title = "Edit";
}

<h2>Edit</h2>

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
    @Html.ValidationSummary(true)

    <fieldset>
        <legend>Instructor</legend>

        @Html.HiddenFor(model => model.InstructorID)
             

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

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

        <div class="editor-label">
            @Html.LabelFor(model => model.HireDate)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.HireDate)
            @Html.ValidationMessageFor(model => model.HireDate)
        </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>

        <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>
        <p>
            <input type="submit" value="Save" />
        </p>
    </fieldset>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

此代码创建一个 HTML 表,其中包含三列。在每一列是其后组成的课程编号和标题的标题的复选框。所有的复选框具有相同的名称 ("selectedCourses"),通知他们将被视为一个组的模型联编程序。每个复选框的value属性设置为的值CourseID.当该页时,模型联编程序将数组传递给控制器CourseID值只选定复选框组成。

复选框最初呈现时,那些分配给教师的课程有checked属性,选择它们 (显示他们检查)。

更改后的课程作业,你会想要能够验证所做的更改,当站点返回到Index 页面。因此,您需要将列添加到该页面中的表。在这种情况下你不需要使用ViewBag对象,因为你想要显示的信息已经被你正在传递到页中的模型作为Instructor实体的Courses导航属性。

Views\Instructor\Index.cshtml,添加课程标题紧接办公室标题,如下面的示例所示:

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

然后添加新的详细信息单元,紧接着办公室位置详细信息单元:

@model ContosoUniversity.ViewModels.InstructorIndexData

@{
    ViewBag.Title = "Instructors";
}

<h2>Instructors</h2>

<p>
    @Html.ActionLink("Create New", "Create")
</p>
<table>
    <tr>
        <th></th>
        <th>Last Name</th>
        <th>First Name</th>
        <th>Hire Date</th>
        <th>Office</th>
        <th>Courses</th>
    </tr>
    @foreach (var item in Model.Instructors)
    {
        string selectedRow = "";
        if (item.InstructorID == ViewBag.InstructorID)
        {
            selectedRow = "selectedrow";
        } 
        <tr class="@selectedRow" valign="top">
            <td>
                @Html.ActionLink("Select", "Index", new { id = item.InstructorID }) | 
                @Html.ActionLink("Edit", "Edit", new { id = item.InstructorID }) | 
                @Html.ActionLink("Details", "Details", new { id = item.InstructorID }) | 
                @Html.ActionLink("Delete", "Delete", new { id = item.InstructorID })
            </td>
            <td>
                @item.LastName
            </td>
            <td>
                @item.FirstMidName
            </td>
            <td>
                @String.Format("{0:d}", item.HireDate)
            </td>
            <td>
                @if (item.OfficeAssignment != null)
                { 
                    @item.OfficeAssignment.Location  
                }
            </td>
            <td>
                @{
                foreach (var course in item.Courses)
                {
                    @course.CourseID @:  @course.Title <br />
                }
                }
            </td>
        </tr> 
    }
</table>

@if (Model.Courses != null)
{ 
    <h3>Courses Taught by Selected Instructor</h3> 
    <table>
        <tr>
            <th></th>
            <th>ID</th>
            <th>Title</th>
            <th>Department</th>
        </tr>

        @foreach (var item in Model.Courses)
        {
            string selectedRow = "";
            if (item.CourseID == ViewBag.CourseID)
            {
                selectedRow = "selectedrow";
            } 
        
            <tr class="@selectedRow">

                <td>
                    @Html.ActionLink("Select", "Index", new { courseID = item.CourseID })
                </td>
                <td>
                    @item.CourseID
                </td>
                <td>
                    @item.Title
                </td>
                <td>
                    @item.Department.Name
                </td>
            </tr> 
        }

    </table> 
}
@if (Model.Enrollments != null)
{ 
    <h3>Students Enrolled in Selected Course</h3> 
    <table>
        <tr>
            <th>Name</th>
            <th>Grade</th>
        </tr>
        @foreach (var item in Model.Enrollments)
        { 
            <tr>
                <td>
                    @item.Student.FullName
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Grade)
                </td>
            </tr> 
        }
    </table> 
}
</td>

运行教练索引页后,可以看到分配给每个教师的课程:

Instructor_index_page

指导员看到编辑页上,请单击编辑

Instructor_edit_page_with_courses

更改某些课程作业并单击保存你所做的更改反映在索引页上。

注: 编辑教练课程数据而采取的做法工作以及为数有限的课程时。对于要大得多的集合,将需要不同的用户界面和不同的更新方法。
 

更新删除方法

改变 HttpPost 删除方法中的代码,所以教练被删除时删除办公室分配记录 (如果有):

[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
   Instructor instructor = db.Instructors
     .Include(i => i.OfficeAssignment)
     .Where(i => i.InstructorID == id)
     .Single();

   instructor.OfficeAssignment = null;
   db.Instructors.Remove(instructor);
   db.SaveChanges();
   return RedirectToAction("Index");
}

如果您尝试删除教练分配到任何部门作为管理员,您将得到一个参照完整性错误。请参阅本教程中的当前版本将自动从任何部门,其中讲师分配作为管理员移除讲师的额外代码。

 

摘要

 

现在,您已完成此简介工作的相关数据。到目前为止这些教程中你做过全范围的 CRUD 操作,但是你没有先处理并发问题。下一个教程将并发的话题引入、 解释选项来处理它,并添加到你已经写出了一种实体类型的 CRUD 代码处理的并发。

posted @ 2015-02-05 17:19  178mz  阅读(2511)  评论(0编辑  收藏  举报