Working with Data » Getting started with ASP.NET Core and Entity Framework Core using Visual Studio

Updating related data

7 of 7 people found this helpful

The Contoso University sample web application demonstrates how to create ASP.NET Core 1.0 MVC web applications using Entity Framework Core 1.0 and Visual Studio 2015. For information about the tutorial series, see the first tutorial in the series.html

In the previous tutorial you displayed related data; in this tutorial you’ll update related data by updating foreign key fields and navigation properties.ios

在前面的教程中完成了顯示關係數據的工做。在本教程中,將經過更新外鍵字段和導航屬性更新關係數據。web

The following illustrations show some of the pages that you’ll work with.redis

下面的幾張圖展現了將要處理的幾個頁面。數據庫

Sections:c#

Customize the Create and Edit Pages for Courses 自定義Course的Create和Edit頁面

When a new course entity is created, it must have a relationship to an existing department. To facilitate this, the scaffolded code includes controller methods and Create and Edit views that include a drop-down list for selecting the department. The drop-down list sets the Course.DepartmentID foreign key property, and that’s all the Entity Framework needs in order to load the Department navigation property with the appropriate Department entity. You’ll use the scaffolded code, but change it slightly to add error handling and sort the drop-down list.併發

建立一個課程實體時,必須有一個指向已有系的關係。爲了實現該功能,基架生成的控制器內的方法以及Create和Edit視圖,包含一個下拉列表以選擇系的信息。下拉列表設置了Course.DepartmentID外鍵屬性,這就是全部EF所須要的------爲了加載適當的Department實體的Department導航屬性。mvc

In CoursesController.cs, delete the four Create and Edit methods and replace them with the following code:app

在CoursesController.cs文件中,刪除四個Create以及Edit方法,將其替換爲下來代碼:asp.net

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

After the Edit HttpPost method, create a new method that loads department info for the drop-down list.

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

The PopulateDepartmentsDropDownList method gets a list of all departments sorted by name, creates a SelectList collection for a drop-down list, and passes the collection to the view in ViewBag. The method accepts the optional selectedDepartment parameter that allows the calling code to specify the item that will be selected when the drop-down list is rendered. The view will pass the name 「DepartmentID」 to the <select> tag helper, and the helper then knows to look in the ViewBag object for a SelectList named 「DepartmentID」.

PopulateDepartmentsDropDownList 方法獲取按名字排序的系名錶,爲下拉列表新建了一個SelectList集合,並將集合傳遞給ViewBag中的視圖。該方法接收可選的SelectedDepartment參數,當下拉列表完成指定後,該參數容許調用的代碼指定被選擇的項目。視圖將把「DepartmentID」名傳遞給<select>標籤助手,接着標籤助手就知道從ViewBag對象中找到名爲「DepartmentID」的SelectList

The HttpGet Create method calls the PopulateDepartmentsDropDownList method without setting the selected item, because for a new course the department is not established yet:

HttpGet的Create方法調用PopulateDepartmentsDropDownList 方法時沒有設置被選項,由於對於一個新課程來講,尚未設置系信息:

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

The HttpGet Edit method sets the selected item, based on the ID of the department that is already assigned to the course being edited:

HttpGet的Edit方法基於系的ID設置被選項,該ID值已經指向了正在編輯中的課程:

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

The HttpPost methods for both Create and Edit also include code that sets the selected item when they redisplay the page after an error. This ensures that when the page is redisplayed to show the error message, whatever department was selected stays selected.

在一個錯誤後再次顯示在頁面上時,Create和Edit的HttpPost方法一樣也包括了設置被選項的代碼。這樣就保證了當頁面被從新顯示一個錯誤信息時,不管選擇了哪一個系,將仍然停留在被選項上。

Add eager loading to Details and Delete methods 向Detail和Delete方法添加預加載

To enable the Course Details and Delete pages to display department data, open CoursesController.cs and add eager loading for department data, as shown below. Also add AsNoTracking to optimize performance.

要使課程的Details和Delete頁面顯示系的數據,請打開CoursesController.cs 文件,接着爲系的數據添加預加載功能,以下所示。一樣爲了優化性能添加AsNoTracking

public async Task<IActionResult> Details(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);
}



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

 

Modify the Course views 修改Course視圖

In Views/Courses/Create.cshtml, add a field for the course ID before the Credits field:

Views/Courses/Create.cshtml中,在Credits字段前添加一個course ID字段:

<div class="form-group">
    <label asp-for="CourseID" class="col-md-2 control-label"></label>
    <div class="col-md-10">
        <input asp-for="CourseID" class="form-control" />
        <span asp-validation-for="CourseID" class="text-danger" />
    </div>
</div>

The scaffolder doesn’t scaffold a primary key because typically the key value is generated by the database and can’t be changed and isn’t a meaningful value to be displayed to users. For Course entities you do need a text box in the Create view for the CourseID field because the DatabaseGeneratedOption.None attribute means the user enters the primary key value.

基架不會爲主鍵建立代碼,由於通常狀況下主鍵值是由數據庫產生的,而且不能被改變,還有對用戶來講,它不表明着具體的什麼含義。對課程實體來講,在Create視圖中確實須要一個文本框來顯示CourseID,由於DatabaseGeneratedOption.None 屬性意味着需由用戶輸入主鍵的值。

In Views/Courses/Create.cshtml, add a 「Select Department」 option to the Department drop-down list, and change the caption for the field from DepartmentID to Department.

Views/Courses/Create.cshtml中,給Department下拉列表增長一個「Select Department」選項,而後將表頭字段從DepartmentID變動爲Department

<div class="form-group">
    <label asp-for="Department" class="col-md-2 control-label"></label>
    <div class="col-md-10">
        <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>
</div>

In Views/Courses/Edit.cshtml, make the same change for the Department field that you just did in Create.cshtml.

在Views/Courses/Edit.cshtml中, 作一樣的變動。

Also in Views/Courses/Edit.cshtml, add a course number field before the Credits field. Because it’s the primary key, it’s displayed, but it can’t be changed.

一樣在View/Courses/Edit.cshtml中,在Credits字段前面添加課程代碼字段。由於它是主鍵,同時也被顯示出來,但並不能被改變。

<div class="form-group">
    <label asp-for="CourseID" class="col-md-2 control-label"></label>
    <div class="col-md-10">
        @Html.DisplayFor(model => model.CourseID)
    </div>
</div>

There’s already a hidden field (<input type="hidden">) for the course number in the Edit view. Adding a <label> tag helper doesn’t eliminate the need for the hidden field because it doesn’t cause the course number to be included in the posted data when the user clicks Save on the Edit page.

在Edit視圖中已經有了一個處理課程代碼的隱藏字段(<input type="hidden">)。請添加一個<label>標籤助手,同時不要忽略對該隱藏字段的需求,由於當點擊Edit頁面上的Save按鈕時,該隱藏字段所包含的課程編碼不會包含在發送的數據中。

In Views/Course/Delete.cshtml, add a course number field at the top and a department name field before the title field.

在Views/Course/delete.cshtml中,向頂部添加一個課程編碼字段,並在表頭字段前增長系名稱字段。

<dl class="dl-horizontal">
    <dt>
        @Html.DisplayNameFor(model => model.CourseID)
    </dt>
    <dd>
        @Html.DisplayFor(model => model.CourseID)
    </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>
    <dt>
        @Html.DisplayNameFor(model => model.Title)
    </dt>
    <dd>
        @Html.DisplayFor(model => model.Title)
    </dd>
</dl>

In Views/Course/Details.cshtml, make the same change that you just did for Delete.cshtml.

Test the Course pages 測試Course頁面

Run the Create page (display the Course Index page and click Create New) and enter data for a new course:

運行Create頁面(顯示Course Index頁面,並點擊Create New),而後輸入新的課程數據:

Click Create. The Courses Index page is displayed with the new course added to the list. The department name in the Index page list comes from the navigation property, showing that the relationship was established correctly.

點擊Create。課程Index頁面顯示出來,其中有已添加進表中的新課程。Index頁面中所屬系的名字來自於導航屬性,顯示了正確的關係配置。

Run the Edit page (click Edit on a course in the Course Index page ).

運行Edit頁面(點擊課程Index頁面中課程後面的Edit)。

Change data on the page and click Save. The Courses Index page is displayed with the updated course data.

Add an Edit Page for Instructors 給Instructors添加編輯頁面

When you edit an instructor record, you want to be able to update the instructor’s office assignment. The Instructor entity has a one-to-zero-or-one relationship with the OfficeAssignment entity, which means your code has to handle the following situations:

當編輯一條講師記錄時,你想更新講師辦公室的安排。講師實體與辦公室安排實體之間有1-0或-1的關係,這意味着你的代碼必須處理一下問題:

  • If the user clears the office assignment and it originally had a value, delete the OfficeAssignment entity.
  • 若是用戶清楚辦公室的安排,而且其本來就有一個值,須要刪除辦公室安排實體。
  • If the user enters an office assignment value and it originally was empty, create a new OfficeAssignment entity.
  • 若是用戶輸入一個辦公室安排的值,而且其原始值是空的,須要建立一個新的辦公室安排實體。
  • If the user changes the value of an office assignment, change the value in an existing OfficeAssignment entity.
  • 若是用戶變動了辦公室安排實體的值,須要變動已有辦公室安排實體的值。

Update the Instructors controller 更新講師控制器

In InstructorsController.cs, change the code in the HttpGet Edit method so that it loads the Instructor entity’s OfficeAssignment navigation property and calls AsNoTracking:

InstructorsController.cs中,將代碼變成HttpGet的Edit方法,以便加載講師實體的辦公室安排導航屬性,而且要調用AsNoTarcking

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

Replace the HttpPost Edit method with the following code to handle office assignment updates:

用下列代碼替換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)
        .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("Index");
    }
    return View(instructorToUpdate);
}

The code does the following:

該代碼段作了以下工做:

  • Changes the method name to EditPost because the signature is now the same as the HttpGet Edit method (the ActionName attribute specifies that the /Edit/ URL is still used).

  • 變動了EditPost方法的名稱,由於與HttpGet的Edit方法相同(ActionName屬性指定仍然使用/Edit/url)。
  • Gets the current Instructor entity from the database using eager loading for the OfficeAssignment navigation property. This is the same as what you did in the HttpGet Edit method.

  • 使用預加載的方式將OfficeAssignment導航屬性從數據庫中取出現有講師實體的信息。這與在HttpGet的Edit方法中作的同樣。
  • Updates the retrieved Instructor entity with values from the model binder. The TryUpdateModel overload enables you to whitelist the properties you want to include. This prevents over-posting, as explained in the second tutorial.

  • 從綁定模型中更新取回的講師實體的值。TryUpdateModel重載使你可將想要屬性列入白名單。這樣作會阻止過多發佈,在第二個教程中進行了解釋。

    if (await TryUpdateModelAsync<Instructor>(
        instructorToUpdate,
        "",
        i => i.FirstMidName, i => i.LastName, i => i.HireDate, i => i.OfficeAssignment))

     

  • If the office location is blank, sets the Instructor.OfficeAssignment property to null so that the related row in the OfficeAssignment table will be deleted.

  • 若是辦公室位置是空白的話,將Instructor.OfficeAssignment屬性數值爲null,以便刪除OfficeAssignment表中的關係行。

    if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location))
    {
        instructorToUpdate.OfficeAssignment = null;
    }

     

  • Saves the changes to the database.

  • 將這些變動保存到數據庫中。

Update the Instructor Edit view 更新講師的Edit視圖

In Views/Instructors/Edit.cshtml, add a new field for editing the office location, at the end before the Save button :

View/Instructors/Edit.cshtml文件中,在文件的末尾Save按鈕以前,添加一個編輯辦公室位置的新字段:

<div class="form-group">
    <label asp-for="OfficeAssignment.Location" class="col-md-2 control-label"></label>
    <div class="col-md-10">
        <input asp-for="OfficeAssignment.Location" class="form-control" />
        <span asp-validation-for="OfficeAssignment.Location" class="text-danger" />
    </div>
</div>

 Run the page (select the Instructors tab and then click Edit on an instructor). Change the Office Location and click Save.

運行頁面,變動Office Location,並點擊Save。

Add Course assignments to the Instructor Edit page 向講師的Edit頁面添加課程安排

Instructors may teach any number of courses. Now you’ll enhance the Instructor Edit page by adding the ability to change course assignments using a group of check boxes, as shown in the following screen shot:

講師們能夠教必定數量的課程。如今要增長講師Edit頁面的功能,添加一組複選框實現變動課程安排的功能,下面截屏進行了展現:

The relationship between the Course and Instructor entities is many-to-many. To add and remove relationships, you add and remove entities to and from the InstructorCourses join entity set.

課程和講師實體間有多對多的關係。要添加並刪除關係,就是從InstructorCourse的聯合集合中添加或刪除實體。

The UI that enables you to change which courses an instructor is assigned to is a group of check boxes. A check box for every course in the database is displayed, and the ones that the instructor is currently assigned to are selected. The user can select or clear check boxes to change course assignments. If the number of courses were much greater, you would probably want to use a different method of presenting the data in the view, but you’d use the same method of manipulating a join entity to create or delete relationships.

該界面實現了該功能,即經過一組複選框來變動課程和講師的分配。該界面顯示了表明數據庫中每門課程的一個複選框,當前已分配到該講師的課程的複選框已經選中了。用戶可經過選擇或清除複選框來變動課程安排。若是課程編碼太大了,你有可能想使用不一樣的方法替代視圖中的數據,可是要使用相同的方法操做一個聯合實體來建立或刪除關係。

Update the Instructors controller 更新講師控制器

To provide data to the view for the list of check boxes, you’ll use a view model class.

要向視圖中的複選框列表提供數據,要使用到視圖模型類。

Create AssignedCourseData.cs in the SchoolViewModels folder and replace the existing code with the following code:

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

 In InstructorsController.cs, replace the HttpGet Edit method with the following code. The changes are highlighted.

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.Courses).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.Courses.Select(c => c.Course.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; }

 

The code adds eager loading for the Courses navigation property and calls the new PopulateAssignedCourseData method to provide information for the check box array using the AssignedCourseData view model class.

該代碼增長了Course導航屬性的預加載,調用一個新的PopulateAssignedCourseData 方法(利用AssignedCourseData視圖模型類向複選框隊列提供信息)。

The code in the PopulateAssignedCourseData method reads through all Course entities in order to load a list of courses using the view model class. For each course, the code checks whether the course exists in the instructor’s Courses navigation property. To create efficient lookup when checking whether a course is assigned to the instructor, the courses assigned to the instructor are put into a HashSet collection. The Assigned property is set to true for courses the instructor is assigned to. The view will use this property to determine which check boxes must be displayed as selected. Finally, the list is passed to the view in ViewData.

PopulateAssignedCourseData 方法中的代碼讀取了全部課程實體,其目的是使用視圖模型類加載一個課程列表。對於每一個課程,代碼都檢查該課程是否存在於講師的Courses導航屬性中。檢查一個課程是否分配到了該講師時,要建立高效的查找,分配到該講師的課程被放入了一個HashSet集合中。若是已經完成分配,Assigned屬性被設置爲true。視圖將使用該屬性來肯定設置哪個複選框爲選中狀態。最後,該列表被傳送到ViewData中的視圖。

Next, add the code that’s executed when the user clicks Save. Replace the EditPost method with the following code, and add a new method that updates the Courses navigation property of the Instructor entity.

接下來,添加用戶點擊Save按鈕所執行的代碼。將EditPost方法替換成下列代碼,再添加一個新的方法,來更新講師實體中的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.Courses)
            .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("Index");
    }
    return View(instructorToUpdate);
}



private void UpdateInstructorCourses(string[] selectedCourses, Instructor instructorToUpdate)
{
    if (selectedCourses == null)
    {
        instructorToUpdate.Courses = new List<CourseAssignment>();
        return;
    }

    var selectedCoursesHS = new HashSet<string>(selectedCourses);
    var instructorCourses = new HashSet<int>
        (instructorToUpdate.Courses.Select(c => c.Course.CourseID));
    foreach (var course in _context.Courses)
    {
        if (selectedCoursesHS.Contains(course.CourseID.ToString()))
        {
            if (!instructorCourses.Contains(course.CourseID))
            {
                instructorToUpdate.Courses.Add(new CourseAssignment { InstructorID = instructorToUpdate.ID, CourseID = course.CourseID });
            }
        }
        else
        {

            if (instructorCourses.Contains(course.CourseID))
            {
                CourseAssignment courseToRemove = instructorToUpdate.Courses.SingleOrDefault(i => i.CourseID == course.CourseID);
                _context.Remove(courseToRemove);
            }
        }
    }
}

 

The method signature is now different from the HttpGet Edit method, so the method name changes from EditPost back to Edit.

如今,該方法的簽名已與HttpGet的Edit方法不一樣了,因此該方法的名稱也從EditPost改回Edit。

Since the view doesn’t have a collection of Course entities, the model binder can’t automatically update the Courses navigation property. Instead of using the model binder to update the Courses navigation property, you do that in the new UpdateInstructorCourses method. Therefore you need to exclude the Courses property from model binding. This doesn’t require any change to the code that calls TryUpdateModel because you’re using the whitelisting overload and Courses isn’t in the include list.

由於視圖尚未課程實體集合,綁定模型不能自動地更新Courses導航屬性。不使用綁定模型更新Courses導航屬性,而是要在新的UpdateInstructorCourses 方法中完成該工做。所以,須要在綁定模型中排除Courses屬性。不須要改變任何調用TryUpdateModel的代碼,由於你正在使用白名單重載,而Courses並再也不包含列表中。

If no check boxes were selected, the code in UpdateInstructorCourses initializes the Courses navigation property with an empty collection and returns:

若是複選框沒有被選中,UpdateInstructorCourses 中的代碼用空集初始化並返回Courses導航屬性。

private void UpdateInstructorCourses(string[] selectedCourses, Instructor instructorToUpdate)
{
    if (selectedCourses == null)
    {
        instructorToUpdate.Courses = new List<CourseAssignment>();
        return;
    }

    var selectedCoursesHS = new HashSet<string>(selectedCourses);
    var instructorCourses = new HashSet<int>
        (instructorToUpdate.Courses.Select(c => c.Course.CourseID));
    foreach (var course in _context.Courses)
    {
        if (selectedCoursesHS.Contains(course.CourseID.ToString()))
        {
            if (!instructorCourses.Contains(course.CourseID))
            {
                instructorToUpdate.Courses.Add(new CourseAssignment { InstructorID = instructorToUpdate.ID, CourseID = course.CourseID });
            }
        }
        else
        {

            if (instructorCourses.Contains(course.CourseID))
            {
                CourseAssignment courseToRemove = instructorToUpdate.Courses.SingleOrDefault(i => i.CourseID == course.CourseID);
                _context.Remove(courseToRemove);
            }
        }
    }
}

 The code then loops through all courses in the database and checks each course against the ones currently assigned to the instructor versus the ones that were selected in the view. To facilitate efficient lookups, the latter two collections are stored in HashSet objects.

接下來,該代碼會遍歷數據庫中的全部課程,檢查每門課程是否與視圖中選擇的講師變量匹配。爲了實現高效的遍歷,後面的兩個集合被存儲在HashSet對象中。

If the check box for a course was selected but the course isn’t in the Instructor.Courses navigation property, the course is added to the collection in the navigation property.

若是一門課程的複選框被選中了,可是該課程沒有在Instructor.Courses導航屬性中,則該門課程就會被添加進導航屬性中的集合。

private void UpdateInstructorCourses(string[] selectedCourses, Instructor instructorToUpdate)
{
    if (selectedCourses == null)
    {
        instructorToUpdate.Courses = new List<CourseAssignment>();
        return;
    }

    var selectedCoursesHS = new HashSet<string>(selectedCourses);
    var instructorCourses = new HashSet<int>
        (instructorToUpdate.Courses.Select(c => c.Course.CourseID));
    foreach (var course in _context.Courses)
    {
        if (selectedCoursesHS.Contains(course.CourseID.ToString()))
        {
            if (!instructorCourses.Contains(course.CourseID))
            {
                instructorToUpdate.Courses.Add(new CourseAssignment { InstructorID = instructorToUpdate.ID, CourseID = course.CourseID });
            }
        }
        else
        {

            if (instructorCourses.Contains(course.CourseID))
            {
                CourseAssignment courseToRemove = instructorToUpdate.Courses.SingleOrDefault(i => i.CourseID == course.CourseID);
                _context.Remove(courseToRemove);
            }
        }
    }
}

 If the check box for a course wasn’t selected, but the course is in the Instructor.Courses navigation property, the course is removed from the navigation property.

若是一門課程的複選框沒有被選中,可是該課程已存在於Instructor.courses導航屬性中,該門課程就會被從導航屬性中刪除。

private void UpdateInstructorCourses(string[] selectedCourses, Instructor instructorToUpdate)
{
    if (selectedCourses == null)
    {
        instructorToUpdate.Courses = new List<CourseAssignment>();
        return;
    }

    var selectedCoursesHS = new HashSet<string>(selectedCourses);
    var instructorCourses = new HashSet<int>
        (instructorToUpdate.Courses.Select(c => c.Course.CourseID));
    foreach (var course in _context.Courses)
    {
        if (selectedCoursesHS.Contains(course.CourseID.ToString()))
        {
            if (!instructorCourses.Contains(course.CourseID))
            {
                instructorToUpdate.Courses.Add(new CourseAssignment { InstructorID = instructorToUpdate.ID, CourseID = course.CourseID });
            }
        }
        else
        {

            if (instructorCourses.Contains(course.CourseID))
            {
                CourseAssignment courseToRemove = instructorToUpdate.Courses.SingleOrDefault(i => i.CourseID == course.CourseID);
                _context.Remove(courseToRemove);
            }
        }
    }
}

 

Update the Instructor views 更新講師視圖

In Views/Instructors/Edit.cshtml, add a Courses field with an array of check boxes by adding the following code immediately after the div elements for the Office field and before the div element for the Save button.

在Views/Instuctors/Edit.cshtml文件中,添加帶有一個複選框隊列的Courses字段,添加位置是在office字段的div元素以後,Save按鈕的div元素以前。

Note 注意

Open the file in a text editor such as Notepad to make this change. If you use Visual Studio, line breaks will be changed in a way that breaks the code. If that happens, fix the line breaks so that they look like what you see here. The indentation doesn’t have to be perfect, but the @</tr><tr>, @:<td>, @:</td>, and @:</tr> lines must each be on a single line as shown or you’ll get a runtime error. After editing the file in a text editor, you can open it in Visual Studio, highlight the block of new code, and press Tab twice to line up the new code with the existing code.

請在某些文本編輯器中打開該文件進行修改,例如Notepad等。若是使用VS進行編輯,換行符會發生變化打亂代碼。若是已經這樣作了,將換行符修改爲下面你看到的樣式。縮進並不須要修正,可是@</tr><tr>、 @:<td>、 @:</td>、 以及@:</tr> 等行必須在同一行中,不然將會致使運行時錯誤。在文本編輯器中完成編輯後,再在VS中開打,高亮部分是新代碼,按兩次Tab鍵修正新代碼與源代碼的排列。

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

 

This code creates an HTML table that has three columns. In each column is a check box followed by a caption that consists of the course number and title. The check boxes all have the same name (「selectedCourses」), which informs the model binder that they are to be treated as a group. The value attribute of each check box is set to the value of CourseID. When the page is posted, the model binder passes an array to the controller that consists of the CourseID values for only the check boxes which are selected.

該部分代碼建立了一個三列的HTML表。在每一個列中,有一個複選框,其後跟着由課程編碼和標題組成的標題。全部複選框都有相同的名字「SelectedCourses」,這樣會通知綁定模型將它們做爲一個組來對待。每一個複選框的值都被設置爲一個CourseID的值。當頁面被髮送後,模型綁定向控制器傳遞一個隊列,其中包含了被選中複選框的CourseID值。

When the check boxes are initially rendered, those that are for courses assigned to the instructor have checked attributes, which selects them (displays them checked).

Run the Instructor Index page, and click Edit on an instructor to see the Edit page.

Change some course assignments and click Save. The changes you make are reflected on the Index page.

Note: The approach taken here to edit instructor course data works well when there is a limited number of courses. For collections that are much larger, a different UI and a different updating method would be required.

注意:由於只有有限的課程,因此這裏編輯講師課程數據所採起的方法工做的很是好。對於很是大的集合,將會須要不一樣的界面以及不一樣的更新方法。

Update the Delete page 更新Delete頁面

In InstructorsController.cs, delete the DeleteConfirmed method and insert the following code in its place.

在InstrctorsController.cs中,刪除DeleteConfirmed方法,並在原處插入下列代碼。

[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
    Instructor instructor = await _context.Instructors
        .Include(i => i.Courses)
        .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("Index");
}

 

This code makes the following changes:

該處的代碼作了以下變動:

  • Does eager loading for the Courses navigation property. You have to include this or EF won’t know about related CourseAssignment entities and won’t delete them. To avoid needing to read them here you could configure cascade delete in the database.
  • 完成Courses導航屬性的預加載。你必須這樣作,不然EF不會知道相關的CourseAssignment實體,並不會將其刪除。在這裏要避免讀取這些數據,你能夠將數據庫配置爲級聯刪除。
  • If the instructor to be deleted is assigned as administrator of any departments, removes the instructor assignment from those departments.
  • 若是要刪除的講師是任何系的administrator,從那些系中刪除該講師的安排。

Add office location and courses to the Create page 將辦公室位置以及課程添加到Create頁面

In InstructorController.cs, delete the HttpGet and HttpPost Create methods, and then add the following code in their place:

在instructorcontroller.cs文件中,刪除HttpGet和HttpPost的Create方法,而後在原處添加下列代碼:

public IActionResult Create()
{
    var instructor = new Instructor();
    instructor.Courses = 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.Courses = new List<CourseAssignment>();
        foreach (var course in selectedCourses)
        {
            var courseToAdd = new CourseAssignment { InstructorID = instructor.ID, CourseID = int.Parse(course) };
            instructor.Courses.Add(courseToAdd);
        }
    }
    if (ModelState.IsValid)
    {
        _context.Add(instructor);
        await _context.SaveChangesAsync();
        return RedirectToAction("Index");
    }
    return View(instructor);
}

 This code is similar to what you saw for the Edit methods except that initially no courses are selected. The HttpGet Create method calls the PopulateAssignedCourseData method not because there might be courses selected but in order to provide an empty collection for the foreach loop in the view (otherwise the view code would throw a null reference exception).

該代碼與你在Edit方法中看到的較爲類似,除了初始化中沒有被選擇的課程。HttpGet的Create方法調用PopulateAssignedCouseData方法,不是由於課程有可能被選中,而是爲了給view中的foreach循環提供一個空集合(不然視圖代碼將拋出一個null引用異常)。

The HttpPost Create method adds each selected course to the Courses navigation property before it checks for validation errors and adds the new instructor to the database. Courses are added even if there are model errors so that when there are model errors (for an example, the user keyed an invalid date), and the page is redisplayed with an error message, any course selections that were made are automatically restored.

在檢查驗證錯誤和向數據庫添加新講師數據以前,HttpPose的Create方法向課程導航屬性添加每一個被選中的課程。即便產生的模型錯誤,添加課程信息的操做仍然被執行了,所以,當產生模型錯誤時(例如:用戶鍵入了錯誤的日期),頁面須要再次顯示,並帶有錯誤信息提示,任何已做出的課程選擇都已被自動存儲了。

Notice that in order to be able to add courses to the Courses navigation property you have to initialize the property as an empty collection:

注意到爲了能向課程導航屬性添加課程,你必須將該屬性初始化爲一個空集:

instructor.Courses = new List<Course>();

As an alternative to doing this in controller code, you could do it in the Instructor model by changing the property getter to automatically create the collection if it doesn’t exist, as shown in the following example:

要實現該目的的另外一個方法是,在講師模型中將get屬性修改成自動建立集合(若是不存在的話),以下所示:

private ICollection<Course> _courses;
public ICollection<Course> Courses
{
    get
    {
        return _courses ?? (_courses = new List<Course>());
    }
    set
    {
        _courses = value;
    }
}

 

If you modify the Courses property in this way, you can remove the explicit property initialization code in the controller.

若是用這種方法修改課程屬性,能夠刪除控制器中的屬性的顯式初始化代碼。

In Views/Instructor/Create.cshtml, add an office location text box and check boxes for courses after the hire date field and before the Submit button. As in the case of the Edit page, this will work better if you do it in a text editor such as Notepad.

在View/Instructor/Create.cshtml文件中,在租借日期字段以後,Submit按鈕以前,增長辦公室位置的文本框。如同在Edit頁面中同樣,最好在一個文本編輯器中(例如Notepad)完成該項工做。

<div class="form-group">
    <label asp-for="OfficeAssignment.Location" class="col-md-2 control-label"></label>
    <div class="col-md-10">
        <input asp-for="OfficeAssignment.Location" class="form-control" />
        <span asp-validation-for="OfficeAssignment.Location" class="text-danger" />
    </div>
</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>

 

Test by running the Create page and adding an instructor.

Handling Transactions 處理事務

As explained in the CRUD tutorial, the Entity Framework implicitly implements transactions. For scenarios where you need more control – for example, if you want to include operations done outside of Entity Framework in a transaction – see Transactions.

和在增刪查改教程中解釋的同樣,EF隱式實現事務處理。在須要更多控制的場景下,例如:若是事務中須要在EF以外進行操做,請參看 TanSactions章節。

Summary

You have now completed the introduction to working with related data. In the next tutorial you’ll see how to handle concurrency conflicts.

如今已經完成了關係數據的介紹工做。在下個教程中,你將看到如何處理併發衝突。

 

原文連接

相關文章
相關標籤/搜索