ASP.NET Core MVC 和 Entity Framework Core 入門(mén)教程 - 更新相關(guān)數(shù)據(jù) (七)

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è)面凹联。

image.png

image.png

自定義課程的 "創(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è) CreateEdit 方法,并使用以下代碼替換它們:

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);
}

CreateEdit 方法(HttpPost) 中還包含了當(dāng)頁(yè)面出現(xiàn)錯(cuò)誤時(shí)蹬昌,用于重新顯示頁(yè)面時(shí)設(shè)置 選中 項(xiàng)目的代碼, 以確保當(dāng)頁(yè)面顯示錯(cuò)誤信息時(shí)攀隔,不管原來(lái)選中的部門(mén)是什么皂贩,仍然保持選中的狀態(tài)。

添加 .AsNoTrackingDetailsDelete 方法

為優(yōu)化 DetailsDelete 頁(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ù)假残。

image.png

點(diǎn)擊 Create 按鈕缭贡, 新課程添加到列表并顯示于 Index 頁(yè)面。 列表中的部門(mén)名稱(chēng)來(lái)自于導(dǎo)航屬性辉懒, 可以看出關(guān)系已正確建立阳惹。
Index 頁(yè)面點(diǎn)擊其中一個(gè)課程的 Edit 按鈕。
image.png

修改頁(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 夯秃,由于方法簽名和 HttpGet Edit 一樣座咆,方法名必須不同(C#語(yǔ)法要求)。通過(guò) ActionName 特性標(biāo)簽以指明仍使用 /Edit/ 超鏈接仓洼。
  • 從數(shù)據(jù)庫(kù)中獲取當(dāng)前講師實(shí)體時(shí)介陶,使用貪婪加載同時(shí)獲取 OfficeAssignment 導(dǎo)航屬性。這個(gè)處理和 HttpGet Edit 方法中的一致色建。
  • 使用模型綁定器中的數(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 按鈕:

image.png

在講師編輯頁(yè)面添加課程安排

講師可能教授任意數(shù)目的課程框都。 你現(xiàn)在要修改講師編輯頁(yè)面,使用一組復(fù)選框來(lái)添加課程分配功能呵晨,如下圖所示:

image.png

課程和講師之間的關(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è)面墩衙。

image.png

改變一些課程的分配务嫡,點(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ā)沖突。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市删豺,隨后出現(xiàn)的幾起案子共虑,更是在濱河造成了極大的恐慌,老刑警劉巖呀页,帶你破解...
    沈念sama閱讀 222,590評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件妈拌,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡蓬蝶,警方通過(guò)查閱死者的電腦和手機(jī)尘分,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)丸氛,“玉大人培愁,你說(shuō)我怎么就攤上這事』捍埽” “怎么了定续?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,301評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)禾锤。 經(jīng)常有香客問(wèn)我私股,道長(zhǎng),這世上最難降的妖魔是什么恩掷? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,078評(píng)論 1 300
  • 正文 為了忘掉前任倡鲸,我火速辦了婚禮,結(jié)果婚禮上螃成,老公的妹妹穿的比我還像新娘旦签。我一直安慰自己,他們只是感情好寸宏,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,082評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布宁炫。 她就那樣靜靜地躺著,像睡著了一般氮凝。 火紅的嫁衣襯著肌膚如雪羔巢。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,682評(píng)論 1 312
  • 那天罩阵,我揣著相機(jī)與錄音竿秆,去河邊找鬼。 笑死稿壁,一個(gè)胖子當(dāng)著我的面吹牛幽钢,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播傅是,決...
    沈念sama閱讀 41,155評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼匪燕,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蕾羊!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起帽驯,我...
    開(kāi)封第一講書(shū)人閱讀 40,098評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤龟再,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后尼变,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體利凑,經(jīng)...
    沈念sama閱讀 46,638評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,701評(píng)論 3 342
  • 正文 我和宋清朗相戀三年嫌术,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了哀澈。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,852評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡度气,死狀恐怖日丹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蚯嫌,我是刑警寧澤,帶...
    沈念sama閱讀 36,520評(píng)論 5 351
  • 正文 年R本政府宣布丙躏,位于F島的核電站择示,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏晒旅。R本人自食惡果不足惜栅盲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,181評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望废恋。 院中可真熱鬧谈秫,春花似錦、人聲如沸鱼鼓。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,674評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)迄本。三九已至硕淑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間嘉赎,已是汗流浹背置媳。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,788評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留公条,地道東北人拇囊。 一個(gè)月前我還...
    沈念sama閱讀 49,279評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像靶橱,于是被迫代替她去往敵國(guó)和親寥袭。 傳聞我的和親對(duì)象是個(gè)殘疾皇子路捧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,851評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容