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.

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

在前面的教程中完成了显示关系数据的工作。在本教程中,将通过更新外键字段和导航属性更新关系数据。

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

下面的几张图展示了将要处理的几个页面。

Sections:

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导航属性。

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

在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("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.

现在已经完成了关系数据的介绍工作。在下个教程中,你将看到如何处理并发冲突。

 

原文链接

posted @ 2016-10-28 11:18  jqdy  阅读(360)  评论(0编辑  收藏  举报