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 代碼處理的並發。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM