Contoso 大學(xué)示例 Web 應(yīng)用程序演示如何使用實(shí)體框架(EF)Core 2.0 和 Visual Studio 2017 創(chuàng)建 ASP.NET Core 2.0 MVC Web 應(yīng)用程序掷邦。 如欲了解更多本教程相關(guān)信息棒旗,請(qǐng)參閱 一、入門(mén)
在上一個(gè)教程中,您學(xué)習(xí)了顯示相關(guān)數(shù)據(jù)烘跺。本教程中闹击, 您將通過(guò)更新外鍵字段和導(dǎo)航屬性來(lái)更新相關(guān)數(shù)據(jù)。
以下圖片顯示了您將用到的一些頁(yè)面凹联。
自定義課程的 "創(chuàng)建" 和 "編輯" 頁(yè)面
創(chuàng)建新的課程實(shí)體時(shí)沐兰,必須關(guān)聯(lián)到一個(gè)現(xiàn)有的部門(mén)。為了方便起見(jiàn)蔽挠,腳手架代碼包括控制器方法和創(chuàng)建和編輯視圖住闯,其中包含用于選擇部門(mén)的下拉列表。 下拉列表設(shè)置 Course.DepartmentID
外鍵屬性澳淑,而這正是 Entity Framework 需要以便加載 Department
導(dǎo)航屬性及其對(duì)應(yīng)的 Department
實(shí)體比原。您將使用腳手架代碼,但稍稍更改以添加錯(cuò)誤處理并對(duì)下拉列表進(jìn)行排序杠巡。
在 CoursesController.cs
中量窘,刪除四個(gè) 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()
.SingleOrDefaultAsync(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
.SingleOrDefaultAsync(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) 方法后氢拥, 創(chuàng)建一個(gè)新方法用于加載部門(mén)下拉列表蚌铜。
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
首先獲取一個(gè)基于名稱(chēng)排序的部門(mén)列表锨侯,再創(chuàng)建一個(gè) SelectList
集合用于下拉列表,并此集合放在 ViewBag 中厘线。 方法中 selectedDepartment
參數(shù)是可選的识腿,允許調(diào)用代碼指定下拉列表默認(rèn)選中項(xiàng)目。視圖中將 DepartmentID
屬性傳遞到 <select>
標(biāo)記幫助器造壮,幫助器知道如何在 ViewBag 對(duì)象中尋找一個(gè)名為 "DepartmentID" 的 SelectList
渡讼。
HttpGet Create
方法調(diào)用 PopulateDepartmentsDropDownList
方法,未設(shè)置選擇項(xiàng)目耳璧, 因?yàn)樵谝粋€(gè)新的課程中成箫,相關(guān)的部門(mén)尚未被創(chuàng)建。
public IActionResult Create()
{
PopulateDepartmentsDropDownList();
return View();
}
HttpGet Edit
方法使用課程分配的部門(mén)ID來(lái)設(shè)置 選中
的項(xiàng)目旨枯。
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var course = await _context.Courses
.AsNoTracking()
.SingleOrDefaultAsync(m => m.CourseID == id);
if (course == null)
{
return NotFound();
}
PopulateDepartmentsDropDownList(course.DepartmentID);
return View(course);
}
Create
及Edit
方法(HttpPost) 中還包含了當(dāng)頁(yè)面出現(xiàn)錯(cuò)誤時(shí)蹬昌,用于重新顯示頁(yè)面時(shí)設(shè)置 選中
項(xiàng)目的代碼, 以確保當(dāng)頁(yè)面顯示錯(cuò)誤信息時(shí)攀隔,不管原來(lái)選中的部門(mén)是什么皂贩,仍然保持選中的狀態(tài)。
添加 .AsNoTracking
到 Details
及 Delete
方法
為優(yōu)化 Details
及 Delete
頁(yè)面的性能昆汹,在方法中添加 AsNoTracking
調(diào)用明刷。
{
if (id == null)
{
return NotFound();
}
var course = await _context.Courses
.Include(c => c.Department)
.AsNoTracking()
.SingleOrDefaultAsync(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()
.SingleOrDefaultAsync(m => m.CourseID == id);
if (course == null)
{
return NotFound();
}
return View(course);
}
修改 Course (課程)
視圖
在 Views/Courses/Create.cshtml
文件中, 添加一個(gè) Select Department
選項(xiàng)到 Department (部門(mén))
下拉框满粗, 將標(biāo)題從 DepartmentID
修改為 Department
辈末,并加入驗(yàn)證信息顯示項(xiàng)目。
<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" />
在 Views/Courses/Edit.cshtml
映皆,進(jìn)行和更改在 Create.cshtml
中一樣的修改挤聘。
同時(shí), 在 Title
字段前添加一個(gè) 課程號(hào)碼
字段捅彻,由于課程號(hào)碼是主鍵组去, 只用于顯示,不能被修改步淹。
<div class="form-group">
<label asp-for="CourseID" class="control-label"></label>
<div>@Html.DisplayFor(model => model.CourseID)</div>
</div>
在 Edit
視圖中添怔,有一個(gè)隱藏的課程號(hào)碼字段(<input type="hidden"
)。添加 <label>
標(biāo)簽后贤旷,隱藏字段仍然有用,因?yàn)樵谟脩?hù)點(diǎn)擊保存時(shí)砾脑,此字段將包含在提交的數(shù)據(jù)中幼驶。
在 Views/Courses/Delete.cshtl
文件中, 在頂部添加一個(gè) 課程號(hào)碼
字段韧衣,并修改 部門(mén)ID
為 部門(mén)名稱(chēng)
盅藻。
@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="dl-horizontal">
<dt>
@Html.DisplayNameFor(model => model.CourseID)
</dt>
<dd>
@Html.DisplayFor(model => model.CourseID)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Title)
</dt>
<dd>
@Html.DisplayFor(model => model.Title)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Credits)
</dt>
<dd>
@Html.DisplayFor(model => model.Credits)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Department)
</dt>
<dd>
@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
文件中购桑,做同樣的修改。
測(cè)試 Course (課程)
頁(yè)面
運(yùn)行應(yīng)用氏淑, 選擇 Courses
菜單勃蜘, 點(diǎn)擊 Create New
, 并輸入一個(gè)新課程數(shù)據(jù)假残。
點(diǎn)擊
Create
按鈕缭贡, 新課程添加到列表并顯示于 Index
頁(yè)面。 列表中的部門(mén)名稱(chēng)來(lái)自于導(dǎo)航屬性辉懒, 可以看出關(guān)系已正確建立阳惹。在
Index
頁(yè)面點(diǎn)擊其中一個(gè)課程的 Edit
按鈕。修改頁(yè)面中的數(shù)據(jù)眶俩,點(diǎn)擊
Save
莹汤。 可以看到 Index
頁(yè)面顯示的是更改修改過(guò)的數(shù)據(jù)。
添加講師的編輯頁(yè)面
當(dāng)你在編輯一個(gè)講師的記錄時(shí)颠印,希望同時(shí)可以更新講師的辦公室纲岭。講師實(shí)體包含一個(gè)和辦公室分配實(shí)體的 一 對(duì) 零或一
關(guān)系, 這意味著代碼中必須處理以下情況:
- 如果用戶(hù)去除了辦公室分配线罕,而原來(lái)是有辦公室分配的止潮,則需要?jiǎng)h除
OfficeAssignment
實(shí)體。 - 如果用戶(hù)輸入了辦公室分配闻坚,而原先是空值沽翔,則需要?jiǎng)?chuàng)建一個(gè)
OfficeAssignment
實(shí)體。 - 如果用戶(hù)修改了辦公室分配的值窿凤,則需要相應(yīng)修改現(xiàn)存的
OfficeAssignment
實(shí)體仅偎。
修改講師控制器
在 InstructorsController.cs
文件中, 修改 Edit
(HttpGet) 方法的代碼雳殊,以同時(shí)獲取 講師實(shí)體的 OfficeAssignment
導(dǎo)航屬性并調(diào)用 AsNoTracking
:
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var instructor = await _context.Instructors
.Include(i => i.OfficeAssignment)
.AsNoTracking()
.SingleOrDefaultAsync(m => m.ID == id);
if (instructor == null)
{
return NotFound();
}
return View(instructor);
}
用以下代碼替換 HttpPost Edit
方法橘沥, 以處理 Office Assignment 的數(shù)據(jù)更新:
[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)
.SingleOrDefaultAsync(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);
}
代碼中,實(shí)現(xiàn)了如下述中修改:
- 修改方法名為
EditPost
夯秃,由于方法簽名和 HttpGetEdit
一樣座咆,方法名必須不同(C#語(yǔ)法要求)。通過(guò)ActionName
特性標(biāo)簽以指明仍使用/Edit/
超鏈接仓洼。 - 從數(shù)據(jù)庫(kù)中獲取當(dāng)前講師實(shí)體時(shí)介陶,使用貪婪加載同時(shí)獲取
OfficeAssignment
導(dǎo)航屬性。這個(gè)處理和 HttpGetEdit
方法中的一致色建。 - 使用模型綁定器中的數(shù)據(jù)更新獲取的講師實(shí)體哺呜。
TryUpdateMOdel
重載允許你指定屬性白名單。 這可以防止Over-Posting
攻擊箕戳。(詳情請(qǐng)查看 第二章 )某残。
if (await TryUpdateModelAsync<Instructor>(
instructorToUpdate,
"",
i => i.FirstMidName, i => i.LastName, i => i.HireDate, i => i.OfficeAssignment))
- 如果辦公室地點(diǎn)是空值国撵, 則設(shè)置
Instructor.OfficeAssignment
屬性為空,這樣一來(lái)玻墅,在OfficeAssignment 數(shù)據(jù)表中的相關(guān)記錄行將會(huì)被刪除介牙。
if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location))
{
instructorToUpdate.OfficeAssignment = null;
}
- 保存更改到數(shù)據(jù)庫(kù)。
*** 更新講師編輯視圖
在Views/Instructors/Edit.cshtml
文件中, 添加一個(gè)新字段,用于編輯辦公室地點(diǎn)
本辐。 放在Save
按鈕之前。
<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>
運(yùn)行應(yīng)用喳整,選擇 Instructors
菜單, 點(diǎn)擊任意一個(gè)講師行中的 Edit
裸扶, 修改 Office Location 數(shù)據(jù)并點(diǎn)擊 Save
按鈕:
在講師編輯頁(yè)面添加課程安排
講師可能教授任意數(shù)目的課程框都。 你現(xiàn)在要修改講師編輯頁(yè)面,使用一組復(fù)選框來(lái)添加課程分配功能呵晨,如下圖所示:
課程和講師之間的關(guān)系是多對(duì)多魏保。 為了添加和移除關(guān)系,你可以通過(guò)從
CourseAssignments
連接實(shí)體集中添加和移除實(shí)體來(lái)實(shí)現(xiàn)摸屠。用戶(hù)界面通過(guò)一組復(fù)選框來(lái)讓你修改講師分配到的課程谓罗。 數(shù)據(jù)庫(kù)中的所有課程都以一個(gè)復(fù)選框的形式顯示, 同時(shí)當(dāng)前已被分配到講師的課程將會(huì)處于選中狀態(tài)季二。 用戶(hù)通過(guò)選中或者去除復(fù)選框的選擇狀態(tài)來(lái)修改課程分配檩咱。 如果課程數(shù)目很多的話(huà),你可能需要使用另外一種視圖展示方式胯舷,但你還是可以使用同一個(gè)方法創(chuàng)建和刪除關(guān)系刻蚯。
修改講師控制器
為了給視圖中的復(fù)選框提供數(shù)據(jù),你將使用視圖模型桑嘶。
在 SchoolViewModels
文件夾中創(chuàng)建 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()
.SingleOrDefaultAsync(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;
}
代碼添加了 Course
導(dǎo)航屬性的貪婪加載逃顶,并調(diào)用 PopulateAssignedCourseData
方法以使用 AssignedCoursData
視圖模型讨便。
代碼中的 PopulateAssignedCourseData
方法遍歷所有的課程實(shí)體。 對(duì)于每一個(gè)課程以政,代碼檢查課程是否在當(dāng)前講師的課程導(dǎo)航屬性中霸褒。 在檢查課程是否分配到講師時(shí),為了實(shí)現(xiàn)高效的查找盈蛮,講師分配的課程被放入一個(gè) Hashset 集合傲霸。 分配給講師的課程對(duì)應(yīng)的 Assigned
屬性設(shè)置為 True
。 視圖將使用此屬性確定哪些復(fù)選框必須顯示為 已選中
狀態(tài)。最后面昙啄,該列表通過(guò) ViewData 傳遞到視圖。
接下來(lái)寸五, 添加用戶(hù)點(diǎn)擊 Save
時(shí)將會(huì)執(zhí)行的代碼梳凛。 使用以下代碼替換 EditPost
方法, 并添加一個(gè)用于更新講師實(shí)體中課程導(dǎo)航屬性的方法梳杏。
[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)
.SingleOrDefaultAsync(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.SingleOrDefault(i => i.CourseID == course.CourseID);
_context.Remove(courseToRemove);
}
}
}
}
方法簽名現(xiàn)在與 HttpGet Edit
方法不同韧拒,所以方法名稱(chēng)可以從 EditPost
更改回 Edit
。
由于視圖中不包含課程實(shí)體集合十性, 模型綁定器無(wú)法自動(dòng)更新 CourseAssignments
導(dǎo)航屬性叛溢。 要取代用模型綁定器更新 CourseAssignments
導(dǎo)航屬性, 你需要一個(gè)新的 UpdateInstructorCourses
方法劲适。 因此楷掉,你需要從模型綁定中排除 CourseAssignments
屬性。這不需要對(duì)調(diào)用 TryUpdateModel
的代碼進(jìn)行任何更改霞势,因?yàn)槟褂玫氖前酌麊纬d烹植,而 CourseAssignments
不在包含列表中。
如果未選中復(fù)選框愕贡,則 UpdateInstructorCourses
中的代碼將使用空集合初始化 CourseAssignments
導(dǎo)航屬性草雕,并結(jié)束方法調(diào)用:
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.SingleOrDefault(i => i.CourseID == course.CourseID);
_context.Remove(courseToRemove);
}
}
}
}
代碼循環(huán)遍歷數(shù)據(jù)庫(kù)中的所有課程,然后代碼循環(huán)遍歷數(shù)據(jù)庫(kù)中的所有課程固以,并檢查每個(gè)課程與當(dāng)前分配給教師的課程墩虹,還是在視圖中選擇的課程。 為了便于高效查找憨琳,后兩個(gè)集合存儲(chǔ)在HashSet對(duì)象中诫钓。
如果有一個(gè)課程的復(fù)選框被選中,但該課程不在 Instructor.CourseAssignments
導(dǎo)航屬性中栽渴,則該課程將添加到導(dǎo)航屬性的集合中尖坤。
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.SingleOrDefault(i => i.CourseID == course.CourseID);
_context.Remove(courseToRemove);
}
}
}
}
更改 Instructor 視圖
在 Views/Instrucotrs/Edit.cshtml
中, 使用一組復(fù)選框添加一個(gè) Courses
字段闲擦,在 Office
字段 div
元素之后慢味, Save
按鈕的 div
元素之前加入如下代碼:
備注
當(dāng)你將代碼粘貼到 Visual Studio 時(shí),分行符將會(huì)更改從而導(dǎo)致代碼出現(xiàn)問(wèn)題墅冷。 通過(guò)按一次 Ctrl + Z 撤消自動(dòng)格式設(shè)置纯路。 這將修復(fù)分行符,使它們看起來(lái)像你在這兒看到的一樣寞忿。 縮進(jìn)可能不甚完美驰唬,但 @</tr><tr>, @:<td>, @:</td>叫编,和 @:</tr> 等行都必須在單獨(dú)的行中出現(xiàn)辖佣,要不然你將發(fā)現(xiàn)一個(gè)運(yùn)行時(shí)錯(cuò)誤。 在選中新代碼塊的情況下搓逾,按 tab 鍵三次以對(duì)齊新代碼與現(xiàn)有代碼的行卷谈。你可以在 這兒 查看與此問(wèn)題相關(guān)的信息。
<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>
此代碼將創(chuàng)建一個(gè) HTML 表格霞篡,包含三列世蔗。 每列中包含 課程編號(hào)、標(biāo)題及一個(gè)復(fù)選框朗兵。 所有的復(fù)選框使用同一個(gè)名稱(chēng) selectedCourses
污淋,這種情況下模型綁定器將它們視為同一組復(fù)選框。 每個(gè)復(fù)選框的值屬性設(shè)置為的值 CourseID
余掖。當(dāng)頁(yè)面提交時(shí)寸爆,模型綁定器將選中的復(fù)選框中包含的 CourseID 值作為一個(gè)數(shù)組發(fā)送到控制器。
當(dāng)復(fù)選框初次渲染時(shí)浊吏,那些被分配到講師的課程有 Checked
特性而昨, 從而以選中樣式顯示。
運(yùn)行應(yīng)用找田,選擇 Instructors 鏈接歌憨, 然后點(diǎn)擊其中一個(gè)講師的 Edit
鏈接,查看編輯頁(yè)面墩衙。
改變一些課程的分配务嫡,點(diǎn)擊
Save
保存修改。 你所做的修改將會(huì)反映在 Index
頁(yè)面中漆改。
備注
此處心铃,因?yàn)檎n程數(shù)量有限,編輯課程數(shù)據(jù)的方法運(yùn)作良好挫剑。對(duì)于相對(duì)大很多的數(shù)據(jù)集合去扣,將需要使用不同的 UI 和不同的更新方法。
更改刪除頁(yè)面
在 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));
}
這段代碼做了如下更改:
- 對(duì)
CourseAssignments
導(dǎo)航屬性使用貪婪加載。 你必須包括這個(gè)屬性哲戚,要不然 EF 不會(huì)知道還存在相關(guān)的CourseAssignments
實(shí)體從而也不會(huì)刪除這些相關(guān)的實(shí)體奔滑。 要避免讀取這些數(shù)據(jù),你可以通過(guò)在數(shù)據(jù)庫(kù)中配置級(jí)聯(lián)刪除達(dá)到同樣的目的顺少。 - 如果要?jiǎng)h除的講師被指定為任何部門(mén)的管理員朋其,從這些部門(mén)中移除這個(gè)講師分配王浴。
添加 辦公室位置和課程到 Create
頁(yè)面
在 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
方法中看到的差不多氓辣,除了沒(méi)有初始選擇的課程外。 HttpGet Create
方法調(diào)用 PopulateAssignedCourseData
方法不是因?yàn)榭赡艽嬖诘恼n程選擇袱蚓,而是為了給視圖中的 foreach
循環(huán)提供一個(gè)空集合(要不然視圖代碼將會(huì)拋出一個(gè)空引用異常)筛婉。
HttpPost Create
方法在檢查驗(yàn)證錯(cuò)誤前,添加課程到 CourseAssignments
導(dǎo)航屬性癞松,然后將新的講師添加到數(shù)據(jù)庫(kù)中。 即使存在模型錯(cuò)誤時(shí)入蛆,課程也將被添加响蓉,這樣一來(lái),當(dāng)發(fā)現(xiàn)模型錯(cuò)誤時(shí)(舉例來(lái)說(shuō)哨毁,用戶(hù)輸入一個(gè)無(wú)效的日期)枫甲,頁(yè)面將會(huì)被重新顯示并包含一個(gè)錯(cuò)誤信息,而之前選擇的課程也將自動(dòng)還原到選擇狀態(tài)扼褪。
請(qǐng)注意想幻,為了可以添加課程到 CourseAssignments
導(dǎo)航屬性,你必須將屬性初始化為一個(gè)空集合话浇。
instructor.CourseAssignments = new List<CourseAssignment>();
如要避免在控制器中出現(xiàn)類(lèi)似代碼脏毯,可行的替換方法是在 Instructor 實(shí)體模型中修改屬性的 getter
方法,當(dāng)集合不存在時(shí)幔崖,自動(dòng)創(chuàng)建食店。
private ICollection<CourseAssignment> _courseAssignments;
public ICollection<CourseAssignment> CourseAssignments
{
get
{
return _courseAssignments ?? (_courseAssignments = new List<CourseAssignment>());
}
set
{
_courseAssignments = value;
}
}
如果你對(duì) CourseAssignments
屬性作此修改,則可以刪除控制器中的顯示屬性初始化代碼赏寇。
在 Views/Instrucotr/Create.cshtml
文件吉嫩,Submit
按鈕前,添加一個(gè) 辦公地點(diǎn)
文本框嗅定,一組課程復(fù)選框自娩。 如同前面的編輯頁(yè)面一樣。
<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>
運(yùn)行應(yīng)用進(jìn)行測(cè)試渠退,創(chuàng)建一個(gè)講師忙迁。
處理事務(wù)
正如在 CRUD 教程 中所解釋的,實(shí)體框架隱式實(shí)現(xiàn)了事務(wù)智什。 如你需要更多更好的控制 -- 例如动漾,你希望在事務(wù)中包含在實(shí)體框架之外完成的操作 - 請(qǐng)參閱 事務(wù)。
小結(jié)
你現(xiàn)在已經(jīng)完成了如何處理相關(guān)數(shù)據(jù)工作的介紹荠锭。 在下一個(gè)教程中旱眯,你將看到如何處理并發(fā)沖突。