教學課程:更新相關資料 - ASP.NET MVC 搭配 EF Core
在先前的教學課程中,您顯示了相關資料。在本教學課程中,您會藉由更新外部索引鍵欄位和導覽屬性來更新相關資料。
下列圖例顯示了您將操作的一些頁面。
在本教學課程中,您已:
- 自訂 Courses 頁面
- 新增 Instructors [編輯] 頁面
- 將課程新增至 [編輯] 頁面
- 更新 [刪除] 頁面
- 將辦公室位置和課程新增至 [建立] 頁面
必要條件
自訂 Courses 頁面
當新的 Course
實體建立時,其必須要與現有的部門具有關聯性。 若要達成此目的,Scaffold 程式碼包含了控制器方法和 [建立] 和 [編輯] 檢視,當中包含了一個可選取部門的下拉式清單。 下拉式清單會設定 Course.DepartmentID
外部索引鍵屬性,以讓 Entity Framework 使用適當的 Department
實體載入 Department
導覽屬性。 您將使用 Scaffold 程式碼,但會稍微對其進行一些變更以新增錯誤處理及排序下拉式清單。
在 CoursesController.cs
中,刪除四個 Create 及 Edit 方法,並以下列程式碼取代:
public IActionResult Create()
{
PopulateDepartmentsDropDownList();
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("CourseID,Credits,DepartmentID,Title")] Course course)
{
if (ModelState.IsValid)
{
_context.Add(course);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
PopulateDepartmentsDropDownList(course.DepartmentID);
return View(course);
}
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var course = await _context.Courses
.AsNoTracking()
.FirstOrDefaultAsync(m => m.CourseID == id);
if (course == null)
{
return NotFound();
}
PopulateDepartmentsDropDownList(course.DepartmentID);
return View(course);
}
[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditPost(int? id)
{
if (id == null)
{
return NotFound();
}
var courseToUpdate = await _context.Courses
.FirstOrDefaultAsync(c => c.CourseID == id);
if (await TryUpdateModelAsync<Course>(courseToUpdate,
"",
c => c.Credits, c => c.DepartmentID, c => c.Title))
{
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists, " +
"see your system administrator.");
}
return RedirectToAction(nameof(Index));
}
PopulateDepartmentsDropDownList(courseToUpdate.DepartmentID);
return View(courseToUpdate);
}
在 Edit
HttpPost 方法後,建立一個新的方法,該方法會將部門資訊載入下拉式清單。
private void PopulateDepartmentsDropDownList(object selectedDepartment = null)
{
var departmentsQuery = from d in _context.Departments
orderby d.Name
select d;
ViewBag.DepartmentID = new SelectList(departmentsQuery.AsNoTracking(), "DepartmentID", "Name", selectedDepartment);
}
PopulateDepartmentsDropDownList
方法會取得依照名稱排序的所有部門清單,為下拉式清單建立 SelectList
集合,然後將集合傳遞給位於 ViewBag
中的檢視。 方法接受選擇性的 selectedDepartment
參數,可允許呼叫程式碼在呈現下拉式清單時指定選取的項目。 檢視會將名稱 "DepartmentID" 傳遞到 <select>
標籤協助程式,協助程式接著便會知道要在 ViewBag
物件中尋找一個名為 "DepartmentID" 的 SelectList
。
HttpGet Create
方法會呼叫 PopulateDepartmentsDropDownList
方法,而不設定選取項目,因為新課程所屬的部門還未建立:
public IActionResult Create()
{
PopulateDepartmentsDropDownList();
return View();
}
HttpGet Edit
方法會根據已指派給正在編輯之課程的部門識別碼來設定選取項目:
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var course = await _context.Courses
.AsNoTracking()
.FirstOrDefaultAsync(m => m.CourseID == id);
if (course == null)
{
return NotFound();
}
PopulateDepartmentsDropDownList(course.DepartmentID);
return View(course);
}
Create
和 Edit
的 HttpPost 方法都同時包含了在錯誤之後重新顯示頁面時設定選取項目的程式碼。 這可確保當頁面重新顯示以顯示錯誤訊息時,任何已選取的部門都會維持該選取狀態。
將 .AsNoTracking 新增至 Details 及 Delete 方法
若要最佳化 Course [詳細資料] 和 [刪除] 頁面的效能,請在 Details
和 HttpGet Delete
方法中新增 AsNoTracking
呼叫。
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
var course = await _context.Courses
.Include(c => c.Department)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.CourseID == id);
if (course == null)
{
return NotFound();
}
return View(course);
}
public async Task<IActionResult> Delete(int? id)
{
if (id == null)
{
return NotFound();
}
var course = await _context.Courses
.Include(c => c.Department)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.CourseID == id);
if (course == null)
{
return NotFound();
}
return View(course);
}
修改 Course 檢視
在 Views/Courses/Create.cshtml
中,將一個「選取部門」選項新增至 [部門] 下拉式清單,將標題從 DepartmentID 變更為 Department,然後新增一個驗證訊息。
<div class="form-group">
<label asp-for="Department" class="control-label"></label>
<select asp-for="DepartmentID" class="form-control" asp-items="ViewBag.DepartmentID">
<option value="">-- Select Department --</option>
</select>
<span asp-validation-for="DepartmentID" class="text-danger" />
</div>
在 Views/Courses/Edit.cshtml
中,為 [部門] 欄位進行您剛剛為 Create.cshtml
進行的相同變更。
同樣的,在 Views/Courses/Edit.cshtml
中,在 [標題] 欄位之前新增一個課程號碼欄位。 由於課程號碼是主索引鍵,雖然會顯示,但您無法變更它。
<div class="form-group">
<label asp-for="CourseID" class="control-label"></label>
<div>@Html.DisplayFor(model => model.CourseID)</div>
</div>
在 [編輯] 檢視中已有一個針對課程號碼的隱藏欄位 (<input type="hidden">
)。 新增 <label>
標籤協助程式無法消除隱藏欄位的必要,因為它無法讓課程號碼包含在使用者按一下位於 [編輯] 頁面上的 [儲存] 時以 Post 方式提交的資料中。
在 Views/Courses/Delete.cshtml
中,在頂端新增一個課程號碼欄位,並將部門識別碼變更為部門名稱。
@model ContosoUniversity.Models.Course
@{
ViewData["Title"] = "Delete";
}
<h2>Delete</h2>
<h3>Are you sure you want to delete this?</h3>
<div>
<h4>Course</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.CourseID)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.CourseID)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Title)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Title)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Credits)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Credits)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Department)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Department.Name)
</dd>
</dl>
<form asp-action="Delete">
<div class="form-actions no-color">
<input type="submit" value="Delete" class="btn btn-default" /> |
<a asp-action="Index">Back to List</a>
</div>
</form>
</div>
在 Views/Courses/Details.cshtml
中,進行您剛才針對 Delete.cshtml
所做的相同變更。
測試 [課程] 頁面
執行應用程式,選取 [Course] 索引標籤,按一下 [新建],並輸入新的課程資料:
按一下 [建立]。 Courses [索引] 頁面便會顯示,並且清單中已有新建立的課程。 [索引] 頁面中的部門名稱來自於導覽屬性,顯示關聯性已正確建立。
按一下 Courses [索引] 頁面中課程的 [編輯]。
變更頁面上的資料,然後按一下 [儲存]。 Courses [索引] 頁面便會顯示,並且清單中已有更新的課程資料。
新增 Instructors [編輯] 頁面
當您編輯講師記錄時,您可能會想要更新講師的辦公室指派。 Instructor
實體與 OfficeAssignment
實體具有一對零或一關聯性,表示您的程式碼必須處理下列狀況:
若使用者清除了原先擁有值的辦公室指派,刪除
OfficeAssignment
實體。若使用者輸入了辦公室指派的值,而該指派原先是空白的,請建立新的
OfficeAssignment
實體。若使用者變更辦公室指派的值,請變更現有
OfficeAssignment
實體中的值。
更新 Instructor 控制器
在 InstructorsController.cs
中,變更 HttpGet Edit
方法中的程式碼,使其載入 Instructor 實體的 OfficeAssignment
導覽屬性,並呼叫 AsNoTracking
:
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var instructor = await _context.Instructors
.Include(i => i.OfficeAssignment)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == id);
if (instructor == null)
{
return NotFound();
}
return View(instructor);
}
使用下列程式碼取代 HttpPost Edit
方法來處理辦公室指派更新:
[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditPost(int? id)
{
if (id == null)
{
return NotFound();
}
var instructorToUpdate = await _context.Instructors
.Include(i => i.OfficeAssignment)
.FirstOrDefaultAsync(s => s.ID == id);
if (await TryUpdateModelAsync<Instructor>(
instructorToUpdate,
"",
i => i.FirstMidName, i => i.LastName, i => i.HireDate, i => i.OfficeAssignment))
{
if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location))
{
instructorToUpdate.OfficeAssignment = null;
}
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists, " +
"see your system administrator.");
}
return RedirectToAction(nameof(Index));
}
return View(instructorToUpdate);
}
程式碼會執行下列操作:
將方法名稱變更為
EditPost
,因為簽章目前與 HttpGetEdit
方法相同 (ActionName
屬性指出/Edit/
URL 仍在使用中)。針對
OfficeAssignment
導覽屬性使用積極式載入從資料庫中取得目前的Instructor
實體。 這與您在 HttpGetEdit
方法中所做的事情一樣。使用從模型繫結器取得的值更新擷取的
Instructor
實體。TryUpdateModel
多載可讓您宣告要包含的屬性。 這可防止大量指派,如同在第二個教學課程中所解釋的。if (await TryUpdateModelAsync<Instructor>( instructorToUpdate, "", i => i.FirstMidName, i => i.LastName, i => i.HireDate, i => i.OfficeAssignment))
如果辦公室位置空白,請將
Instructor.OfficeAssignment
屬性設定為 null,以便刪除OfficeAssignment
資料表中的相關資料列。if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location)) { instructorToUpdate.OfficeAssignment = null; }
將變更儲存到資料庫。
更新 Instructor [編輯] 檢視
在 Views/Instructors/Edit.cshtml
中,在 [儲存] 按鈕前的結尾處新增一個用於編輯辦公室位置的欄位:
<div class="form-group">
<label asp-for="OfficeAssignment.Location" class="control-label"></label>
<input asp-for="OfficeAssignment.Location" class="form-control" />
<span asp-validation-for="OfficeAssignment.Location" class="text-danger" />
</div>
執行應用程式,選取 [Instructor] 索引標籤,然後在講師上按一下 [編輯]。 變更 [辦公室位置],然後按一下 [儲存]。
將課程新增至 [編輯] 頁面
講師可教授任何數量的課程。 現在您將藉由使用核取方塊群組,新增變更課程指派的能力來強化 Instructor [編輯] 頁面,如以下螢幕擷取畫面所示:
在 Course
和 Instructor
實體之間的關聯性為多對多。 若要新增和移除關聯性,您必須往返 CourseAssignments
聯結實體集新增和移除實體。
可讓您變更講師指派之課程的 UI 為一組核取方塊。 資料庫中每個課程的核取方塊都會顯示,而該名講師目前受指派的課程會已選取狀態顯示。 使用者可以選取或清除核取方塊來變更課程指派。 若課程數量要大上許多,您可能會想要使用不同的方法來在檢視中呈現資料,但您操縱聯結實體以建立或刪除關聯性的方法是相同的。
更新 Instructor 控制器
若要為核取方塊清單提供檢視的資料,您必須使用檢視模型類別。
在 SchoolViewModels 資料夾中,建立 AssignedCourseData.cs
,然後使用下列程式碼取代現有的程式碼:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ContosoUniversity.Models.SchoolViewModels
{
public class AssignedCourseData
{
public int CourseID { get; set; }
public string Title { get; set; }
public bool Assigned { get; set; }
}
}
在 InstructorsController.cs
中,以下列程式碼取代 HttpGet Edit
方法。 所做的變更已醒目提示。
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var instructor = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments).ThenInclude(i => i.Course)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == id);
if (instructor == null)
{
return NotFound();
}
PopulateAssignedCourseData(instructor);
return View(instructor);
}
private void PopulateAssignedCourseData(Instructor instructor)
{
var allCourses = _context.Courses;
var instructorCourses = new HashSet<int>(instructor.CourseAssignments.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)
});
}
ViewData["Courses"] = viewModel;
}
程式碼會為 Courses
導覽屬性新增積極式載入,然後使用 AssignedCourseData
檢視模型類別來呼叫新的 PopulateAssignedCourseData
方法以提供資訊給核取方塊陣列。
PopulateAssignedCourseData
方法中的程式碼會讀取所有的 Course
實體以使用檢視模型類別載入課程清單。 針對每個課程,程式碼會檢查課程是否存在於講師的 Courses
導覽屬性中。 為了在檢查課程是否已指派給講師的過程中更有效率,指派給講師的課程會放入一個 HashSet
集合中。 Assigned
屬性會針對已指派給講師的課程設定為 true。 檢視會使用這個屬性,來判斷哪一個核取方塊必須顯示為已選取。 最後,清單會傳遞至位於 ViewData
的檢視中。
接下來,新增當使用者按一下 [儲存] 時要執行的程式碼。 使用下列程式碼取代 EditPost
方法,然後新增一個方法,該方法會更新 Instructor 實體的 Courses
導覽屬性。
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int? id, string[] selectedCourses)
{
if (id == null)
{
return NotFound();
}
var instructorToUpdate = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.FirstOrDefaultAsync(m => m.ID == id);
if (await TryUpdateModelAsync<Instructor>(
instructorToUpdate,
"",
i => i.FirstMidName, i => i.LastName, i => i.HireDate, i => i.OfficeAssignment))
{
if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location))
{
instructorToUpdate.OfficeAssignment = null;
}
UpdateInstructorCourses(selectedCourses, instructorToUpdate);
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists, " +
"see your system administrator.");
}
return RedirectToAction(nameof(Index));
}
UpdateInstructorCourses(selectedCourses, instructorToUpdate);
PopulateAssignedCourseData(instructorToUpdate);
return View(instructorToUpdate);
}
private void UpdateInstructorCourses(string[] selectedCourses, Instructor instructorToUpdate)
{
if (selectedCourses == null)
{
instructorToUpdate.CourseAssignments = new List<CourseAssignment>();
return;
}
var selectedCoursesHS = new HashSet<string>(selectedCourses);
var instructorCourses = new HashSet<int>
(instructorToUpdate.CourseAssignments.Select(c => c.Course.CourseID));
foreach (var course in _context.Courses)
{
if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
if (!instructorCourses.Contains(course.CourseID))
{
instructorToUpdate.CourseAssignments.Add(new CourseAssignment { InstructorID = instructorToUpdate.ID, CourseID = course.CourseID });
}
}
else
{
if (instructorCourses.Contains(course.CourseID))
{
CourseAssignment courseToRemove = instructorToUpdate.CourseAssignments.FirstOrDefault(i => i.CourseID == course.CourseID);
_context.Remove(courseToRemove);
}
}
}
}
方法簽章現在已和 HttpGet Edit
方法不同,因此方法名稱會從 EditPost
變回 Edit
。
由於檢視沒有 Course 實體的集合,模型繫結器無法自動更新 CourseAssignments
導覽屬性。 相較於使用模型繫結器更新 CourseAssignments
導覽屬性,您會在新的 UpdateInstructorCourses
方法中進行相同的操作。 因此您必須從模型繫結中排除 CourseAssignments
屬性。 這不需要對呼叫 TryUpdateModel
的程式碼進行任何變更,因為您使用的是需要明確核准的多載,而 CourseAssignments
未在包含清單中。
如果沒有選取任何核取方塊,UpdateInstructorCourses
中的程式碼會使用空集合初始化 CourseAssignments
導覽屬性並傳回:
private void UpdateInstructorCourses(string[] selectedCourses, Instructor instructorToUpdate)
{
if (selectedCourses == null)
{
instructorToUpdate.CourseAssignments = new List<CourseAssignment>();
return;
}
var selectedCoursesHS = new HashSet<string>(selectedCourses);
var instructorCourses = new HashSet<int>
(instructorToUpdate.CourseAssignments.Select(c => c.Course.CourseID));
foreach (var course in _context.Courses)
{
if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
if (!instructorCourses.Contains(course.CourseID))
{
instructorToUpdate.CourseAssignments.Add(new CourseAssignment { InstructorID = instructorToUpdate.ID, CourseID = course.CourseID });
}
}
else
{
if (instructorCourses.Contains(course.CourseID))
{
CourseAssignment courseToRemove = instructorToUpdate.CourseAssignments.FirstOrDefault(i => i.CourseID == course.CourseID);
_context.Remove(courseToRemove);
}
}
}
}
程式碼會執行迴圈,尋訪資料庫中所有的課程,並檢查每個已指派給講師的課程,以及在檢視中選取的課程。 為了協助達成有效率的搜尋,後者的兩個集合會儲存在 HashSet
物件中。
如果課程的核取方塊已選取,但課程並未位於 Instructor.CourseAssignments
導覽屬性中,則課程便會新增至導覽屬性的集合中。
private void UpdateInstructorCourses(string[] selectedCourses, Instructor instructorToUpdate)
{
if (selectedCourses == null)
{
instructorToUpdate.CourseAssignments = new List<CourseAssignment>();
return;
}
var selectedCoursesHS = new HashSet<string>(selectedCourses);
var instructorCourses = new HashSet<int>
(instructorToUpdate.CourseAssignments.Select(c => c.Course.CourseID));
foreach (var course in _context.Courses)
{
if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
if (!instructorCourses.Contains(course.CourseID))
{
instructorToUpdate.CourseAssignments.Add(new CourseAssignment { InstructorID = instructorToUpdate.ID, CourseID = course.CourseID });
}
}
else
{
if (instructorCourses.Contains(course.CourseID))
{
CourseAssignment courseToRemove = instructorToUpdate.CourseAssignments.FirstOrDefault(i => i.CourseID == course.CourseID);
_context.Remove(courseToRemove);
}
}
}
}
如果課程的核取方塊未選取,但課程卻位於 Instructor.CourseAssignments
導覽屬性中,則課程便會從導覽屬性的集合中移除。
private void UpdateInstructorCourses(string[] selectedCourses, Instructor instructorToUpdate)
{
if (selectedCourses == null)
{
instructorToUpdate.CourseAssignments = new List<CourseAssignment>();
return;
}
var selectedCoursesHS = new HashSet<string>(selectedCourses);
var instructorCourses = new HashSet<int>
(instructorToUpdate.CourseAssignments.Select(c => c.Course.CourseID));
foreach (var course in _context.Courses)
{
if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
if (!instructorCourses.Contains(course.CourseID))
{
instructorToUpdate.CourseAssignments.Add(new CourseAssignment { InstructorID = instructorToUpdate.ID, CourseID = course.CourseID });
}
}
else
{
if (instructorCourses.Contains(course.CourseID))
{
CourseAssignment courseToRemove = instructorToUpdate.CourseAssignments.FirstOrDefault(i => i.CourseID == course.CourseID);
_context.Remove(courseToRemove);
}
}
}
}
更新 Instructor 檢視
在 Views/Instructors/Edit.cshtml
中,藉由將下列程式碼新增到 [辦公室] 欄位的 div
元素後及 [儲存] 按鈕的 div
元素前,來新增 [課程] 欄位與核取方塊陣列。
注意
當您將程式碼貼至 Visual Studio 時,分行符號可能會變更,而讓程式碼斷行。 如果程式碼在貼上之後看起來不同,請按 Ctrl+Z 一次以復原自動格式化。 這會修正分行符號,使他們看起來就跟您在這裡看到的一樣。 縮排不一定要是完美的,但 @:</tr><tr>
、@:<td>
、@:</td>
和 @:</tr>
必須要如顯示般各自在獨立的一行上,否則您會接收到執行階段錯誤。 當選取新的程式碼區塊時,按下 Tab 鍵三次來讓新的程式碼對準現有的程式碼。 Visual Studio 2019 已修正這個問題。
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<table>
<tr>
@{
int cnt = 0;
List<ContosoUniversity.Models.SchoolViewModels.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>
</div>
此程式碼會建立一個 HTML 表格,該表格中有三個資料行。 在每個資料行中,核取方塊的後方會是由課程號碼和標題組成的標題。 所有核取方塊的名稱都是 ("selectedCourses"),會告知模型繫結器應將其視為一個群組。 每個核取方塊的值屬性都會設為值 CourseID
。 在張貼頁面時,模型繫結器便會將只包含我們選取核取方塊 CourseID
值的陣列傳遞到控制器。
核取方塊一開始轉譯時,已指派給該名講師的課程便會帶有已勾選的屬性,使其顯示為已選取狀態。
執行應用程式,選取 [Instructor] 索引標籤,然後按一下講師上的 [編輯] 以查看 [編輯] 頁面。
變更一些課程指派,然後按一下 [儲存]。 您所做的變更會反映在 [索引] 頁面上。
注意
這裡所用來編輯講師課程資料的方法在課程的數量有限時運作相當良好。 針對更大的集合,將需要不同的 UI 和不同的更新方法。
更新 [刪除] 頁面
在 InstructorsController.cs
中,刪除 DeleteConfirmed
方法並在相同位置插入下列程式碼。
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
Instructor instructor = await _context.Instructors
.Include(i => i.CourseAssignments)
.SingleAsync(i => i.ID == id);
var departments = await _context.Departments
.Where(d => d.InstructorID == id)
.ToListAsync();
departments.ForEach(d => d.InstructorID = null);
_context.Instructors.Remove(instructor);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
此程式碼會進行下列變更:
為
CourseAssignments
導覽屬性進行積極式載入。 您必須包含這個,否則 EF 將無法得知相關CourseAssignment
而無法刪除他們。 若要避免在此讀取他們,您可以在資料庫中設定串聯刪除。若要刪除的講師已指派為任何部門的系統管理員,請先從部門中移除講師的指派。
將辦公室位置和課程新增至 [建立] 頁面
在 InstructorsController.cs
中,刪除 HttpGet 和 HttpPost Create
方法,然後在相同位置新增下列程式碼:
public IActionResult Create()
{
var instructor = new Instructor();
instructor.CourseAssignments = new List<CourseAssignment>();
PopulateAssignedCourseData(instructor);
return View();
}
// POST: Instructors/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("FirstMidName,HireDate,LastName,OfficeAssignment")] Instructor instructor, string[] selectedCourses)
{
if (selectedCourses != null)
{
instructor.CourseAssignments = new List<CourseAssignment>();
foreach (var course in selectedCourses)
{
var courseToAdd = new CourseAssignment { InstructorID = instructor.ID, CourseID = int.Parse(course) };
instructor.CourseAssignments.Add(courseToAdd);
}
}
if (ModelState.IsValid)
{
_context.Add(instructor);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
PopulateAssignedCourseData(instructor);
return View(instructor);
}
此程式碼與您在 Edit
方法中看到的類似,除了一開始沒有選取任何課程之外。 HttpGet Create
方法會呼叫 PopulateAssignedCourseData
方法,不是因為可能會有已選取的課程,而是為了提供空集合給檢視中的 foreach
迴圈 (否則檢視程式碼會擲回 Null 參考例外狀況)。
HttpPost Create
方法會在檢查驗證錯誤並將新的講師新增到資料庫前將每個選取的課程新增到 CourseAssignments
導覽屬性中。 即使發生模型錯誤,課程也會新增,這使得當發生模型錯誤 (例如使用者鍵入了無效的日期),並且頁面重新顯示並帶有錯誤訊息時,任何課程選取都會自動還原。
請注意,為了要能夠將課程新增到 CourseAssignments
導覽屬性,您必須將屬性以空集合初始化:
instructor.CourseAssignments = new List<CourseAssignment>();
作為在控制器程式碼中完成這項操作的替代方案,您可以在 Instructor
模型中藉由將屬性 getter 變更為在不存在時自動建立集合來完成,如以下範例所示:
private ICollection<CourseAssignment> _courseAssignments;
public ICollection<CourseAssignment> CourseAssignments
{
get
{
return _courseAssignments ?? (_courseAssignments = new List<CourseAssignment>());
}
set
{
_courseAssignments = value;
}
}
若您使用這種方式修改了 CourseAssignments
屬性,您便可以移除控制器中的明確屬性初始化程式碼。
在 Views/Instructor/Create.cshtml
中,在 [提交] 按鈕前新增一個辦公室位置文字方塊及課程核取方塊。 若為 [編輯] 頁面,請修正 Visual Studio 於您貼上時重新格式化程式碼。
<div class="form-group">
<label asp-for="OfficeAssignment.Location" class="control-label"></label>
<input asp-for="OfficeAssignment.Location" class="form-control" />
<span asp-validation-for="OfficeAssignment.Location" class="text-danger" />
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<table>
<tr>
@{
int cnt = 0;
List<ContosoUniversity.Models.SchoolViewModels.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>
</div>
執行應用程式並建立一名講師,以進行測試。
處理交易
如同在 CRUD 教學課程中所述,Entity Framework 隱含實作了交易。 針對您需要更多控制的案例 -- 例如,若您想要在一個交易中包含在 Entity Framework 之外完成的作業 -- 請參閱交易。
取得程式碼
下一步
在本教學課程中,您已:
- 自訂 Courses 頁面
- 新增 Instructors [編輯] 頁面
- 將課程新增至 [編輯] 頁面
- 更新 [刪除] 頁面
- 將辦公室位置和課程新增至 [建立] 頁面
若要了解如何處理並行衝突,請前往下一個教學課程。