教程:在 ASP.NET MVC 应用中使用 EF 更新相关数据

在上一教程中,你显示了相关数据。 在本教程中,你将更新相关数据。 对于大多数关系,可以通过更新外键字段或导航属性来完成此操作。 对于多对多关系,Entity Framework 不会直接公开联接表,因此可以在相应的导航属性中添加和删除实体。

下图是一些将会用到的页面。

Course_create_page

Instructor_edit_page_with_courses

讲师使用课程进行编辑

在本教程中,你将了解:

  • 自定义课程页面
  • 将办公室添加到讲师页面
  • 向讲师页面添加课程
  • 更新 DeleteConfirmed
  • 向创建页添加办公室位置和课程

先决条件

自定义课程页面

创建新的课程实体时,新实体必须与现有院系有关系。 为此,基架代码需包括控制器方法、创建视图和编辑视图,且视图中应包括用于选择院系的下拉列表。 下拉列表设置了 Course.DepartmentID 外键属性,而这正是 Entity Framework 使用适当的 Department 实体加载 Department 导航属性所需要的。 将用到基架代码,但需对其稍作更改,以便添加错误处理和对下拉列表进行排序。

CourseController.cs中,删除四 Create 个和 Edit 方法,并将其替换为以下代码:

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

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "CourseID,Title,Credits,DepartmentID")]Course course)
{
    try
    {
        if (ModelState.IsValid)
        {
            db.Courses.Add(course);
            db.SaveChanges();
            return RedirectToAction("Index");
        }
    }
    catch (RetryLimitExceededException /* dex */)
    {
        //Log the error (uncomment dex variable name and add a line here to write a log.)
        ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
    }
    PopulateDepartmentsDropDownList(course.DepartmentID);
    return View(course);
}

public ActionResult Edit(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Course course = db.Courses.Find(id);
    if (course == null)
    {
        return HttpNotFound();
    }
    PopulateDepartmentsDropDownList(course.DepartmentID);
    return View(course);
}

[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public ActionResult EditPost(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    var courseToUpdate = db.Courses.Find(id);
    if (TryUpdateModel(courseToUpdate, "",
       new string[] { "Title", "Credits", "DepartmentID" }))
    {
        try
        {
            db.SaveChanges();

            return RedirectToAction("Index");
        }
        catch (RetryLimitExceededException /* dex */)
        {
            //Log the error (uncomment dex variable name and add a line here to write a log.
            ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
        }
    }
    PopulateDepartmentsDropDownList(courseToUpdate.DepartmentID);
    return View(courseToUpdate);
}

private void PopulateDepartmentsDropDownList(object selectedDepartment = null)
{
    var departmentsQuery = from d in db.Departments
                           orderby d.Name
                           select d;
    ViewBag.DepartmentID = new SelectList(departmentsQuery, "DepartmentID", "Name", selectedDepartment);
} 

在文件的开头添加以下 using 语句:

using System.Data.Entity.Infrastructure;

该方法 PopulateDepartmentsDropDownList 获取按名称排序的所有部门的列表,为下拉列表创建集合 SelectList ,并将集合传递到属性中的 ViewBag 视图。 该方法可以使用可选的 selectedDepartment 参数,而调用的代码可以通过该参数来指定呈现下拉列表时被选择的项。 该视图将名称DepartmentID传递给 DropDownList 帮助程序,帮助程序随后知道在对象中ViewBag查找命名DepartmentID对象SelectList

该方法HttpGetCreate在不设置所选项目的情况下调用PopulateDepartmentsDropDownList该方法,因为对于新课程,该部门尚未建立:

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

该方法HttpGetEdit根据已分配给正在编辑的课程的部门 ID 设置所选项目:

public ActionResult Edit(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Course course = db.Courses.Find(id);
    if (course == null)
    {
        return HttpNotFound();
    }
    PopulateDepartmentsDropDownList(course.DepartmentID);
    return View(course);
}

HttpPost两种方法CreateEdit,还包括在错误后重新显示页面时设置选定项的代码:

catch (RetryLimitExceededException /* dex */)
{
    //Log the error (uncomment dex variable name and add a line here to write a log.)
    ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
}
PopulateDepartmentsDropDownList(course.DepartmentID);
return View(course);

此代码可确保重新显示页面以显示错误消息时,选择的任何部门都会保持选中状态。

课程视图已用部门字段的下拉列表搭建基架,但你不希望此字段的 DepartmentID 标题,因此,对 Views\Course\Create.cshtml 文件进行以下突出显示的更改以更改标题。

@model ContosoUniversity.Models.Course

@{
    ViewBag.Title = "Create";
}

<h2>Create</h2>

@using (Html.BeginForm()) 
{
    @Html.AntiForgeryToken()
    
    <div class="form-horizontal">
        <h4>Course</h4>
        <hr />
        @Html.ValidationSummary(true)

        <div class="form-group">
            @Html.LabelFor(model => model.CourseID, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.CourseID)
                @Html.ValidationMessageFor(model => model.CourseID)
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Title, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Title)
                @Html.ValidationMessageFor(model => model.Title)
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Credits, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Credits)
                @Html.ValidationMessageFor(model => model.Credits)
            </div>
        </div>

        <div class="form-group">
            <label class="control-label col-md-2" for="DepartmentID">Department</label>
            <div class="col-md-10">
                @Html.DropDownList("DepartmentID", String.Empty)
                @Html.ValidationMessageFor(model => model.DepartmentID)
            </div>
        </div>

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

在 Views\Course\Edit.cshtml进行相同的更改。

通常基架不会搭建主键基架,因为密钥值由数据库生成,无法更改,并且不是向用户显示有意义的值。 对于 Course 实体,基架确实包含字段的 CourseID 文本框,因为它理解该 DatabaseGeneratedOption.None 属性意味着用户应该能够输入主键值。 但是,它不明白,因为数字是有意义的,你想在其他视图中看到它,所以你需要手动添加它。

Views\Course\Edit.cshtml 中,在“标题”字段之前添加课程编号字段。 因为它是主键,因此会显示它,但无法更改。

<div class="form-group">
    @Html.LabelFor(model => model.CourseID, new { @class = "control-label col-md-2" })
    <div class="col-md-10">
        @Html.DisplayFor(model => model.CourseID)
    </div>
</div>

在“编辑”视图中,课程编号已经有一个隐藏字段(Html.HiddenFor 帮助程序)。 添加 Html.LabelFor 帮助程序不会消除隐藏字段的需求,因为当用户单击“编辑”页面上的“保存时,不会将课程编号包含在已发布的数据中。

Views\Course\Delete.cshtmlViews\Course\Details.cshtml 中,将部门名称标题从“Name”更改为“Department”,并在标题字段之前添加课程编号字段。

<dt>
    Department
</dt>

<dd>
    @Html.DisplayFor(model => model.Department.Name)
</dd>

<dt>
    @Html.DisplayNameFor(model => model.CourseID)
</dt>

<dd>
    @Html.DisplayFor(model => model.CourseID)
</dd>

运行“创建”页(显示“课程索引”页,然后单击“新建)并输入新课程的数据:

设置
Number 输入“1000”。
标题 输入 代数
致谢 输入“4”
Department 选择 数学

单击 “创建” 。 “课程索引”页显示,其中新课程已添加到列表中。 索引页列表中的院系名称来自导航属性,表明已正确建立关系。

运行“编辑”页(显示“课程索引”页,然后单击“在课程上编辑”)。

更改页面上的数据,然后单击“保存”。 “课程索引”页随更新的课程数据一起显示。

将办公室添加到讲师页面

编辑讲师记录时,有时希望能更新讲师的办公室分配。 该 Instructor 实体与 OfficeAssignment 实体有一对零或一的关系,这意味着必须处理以下情况:

  • 如果用户清除了办公室分配,并且它最初具有值,则必须删除和删除实体 OfficeAssignment
  • 如果用户输入了办公室分配值,并且最初为空,则必须创建新 OfficeAssignment 实体。
  • 如果用户更改了办公室分配的值,则必须更改现有 OfficeAssignment 实体中的值。

打开InstructorController.cs并查看HttpGetEdit方法:

{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Instructor instructor = db.Instructors.Find(id);
    if (instructor == null)
    {
        return HttpNotFound();
    }
    ViewBag.ID = new SelectList(db.OfficeAssignments, "InstructorID", "Location", instructor.ID);
    return View(instructor);
}

此处的基架代码不是所需的代码。 它正在为下拉列表设置数据,但你需要的是文本框。 将此方法替换为以下代码:

public ActionResult Edit(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Instructor instructor = db.Instructors
        .Include(i => i.OfficeAssignment)
        .Where(i => i.ID == id)
        .Single();
    if (instructor == null)
    {
        return HttpNotFound();
    }
    return View(instructor);
}

此代码删除 ViewBag 该语句,并添加关联 OfficeAssignment 实体的预先加载。 不能使用 Find 该方法执行预先加载,因此 Where 使用和 Single 方法来选择讲师。

HttpPostEdit方法替换为以下代码。 用于处理办公室分配更新:

[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public ActionResult EditPost(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    var instructorToUpdate = db.Instructors
       .Include(i => i.OfficeAssignment)
       .Where(i => i.ID == id)
       .Single();

    if (TryUpdateModel(instructorToUpdate, "",
       new string[] { "LastName", "FirstMidName", "HireDate", "OfficeAssignment" }))
    {
       try
       {
          if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment.Location))
          {
             instructorToUpdate.OfficeAssignment = null;
          }

          db.SaveChanges();

          return RedirectToAction("Index");
       }
       catch (RetryLimitExceededException /* dex */)
      {
         //Log the error (uncomment dex variable name and add a line here to write a log.
         ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
      }
   }
   return View(instructorToUpdate);
}

对语句的using引用RetryLimitExceededException;若要添加语句,请将鼠标悬停在鼠标上方RetryLimitExceededException。 将显示以下消息:  重试异常消息

选择“ 显示潜在修补程序”,然后使用 System.Data.Entity.Infrastructure

解决重试异常

该代码执行以下操作:

  • 将方法名称 EditPost 更改为,因为签名现在与 HttpGet 方法相同( ActionName 属性指定仍使用 /Edit/ URL)。

  • 使用 OfficeAssignment 导航属性的预先加载从数据库获取当前的 Instructor 实体。 这与在方法中HttpGetEdit执行的操作相同。

  • 用模型绑定器中的值更新检索到的 Instructor 实体。 使用 TryUpdateModel 重载可以列出要包含的属性。 这样可以防止第二个教程中所述的过度发布。

    if (TryUpdateModel(instructorToUpdate, "",
          new string[] { "LastName", "FirstMidName", "HireDate", "OfficeAssignment" }))
    
  • 如果办公室位置为空,请将 Instructor.OfficeAssignment 属性设置为 NULL,以便删除 OfficeAssignment 表中的相关行。

    if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment.Location))
    {
        instructorToUpdate.OfficeAssignment = null;
    }
    
  • 将更改保存到数据库。

Views\Instructor\Edit.cshtml,在“雇用日期”字段的元素之后div,添加用于编辑办公室位置的新字段:

<div class="form-group">
    @Html.LabelFor(model => model.OfficeAssignment.Location, new { @class = "control-label col-md-2" })
    <div class="col-md-10">
        @Html.EditorFor(model => model.OfficeAssignment.Location)
        @Html.ValidationMessageFor(model => model.OfficeAssignment.Location)
    </div>
</div>

运行页面(选择“讲师”选项卡,然后单击讲师上的“编辑)。 更改“办公室位置”,然后单击“保存” 。

向讲师页面添加课程

讲师可能教授任意数量的课程。 现在,你将通过添加使用一组复选框更改课程作业的功能来增强讲师编辑页面。

实体Instructor之间的关系Course是多对多,这意味着你无权直接访问联接表中的外键属性。 而是在导航属性中添加 Instructor.Courses 和删除实体。

用于更改讲师所对应的课程的 UI 是一组复选框。 该复选框中会显示数据库中的所有课程,选中讲师当前对应的课程即可。 用户可以通过选择或清除复选框来更改课程分配。 如果课程数量要大得多,你可能希望使用不同的方法在视图中呈现数据,但你会使用相同的操作导航属性方法来创建或删除关系。

若要为复选框列表的视图提供数据,将使用视图模型类。 在 ViewModels 文件夹中创建AssignedCourseData.cs,并将现有代码替换为以下代码:

namespace ContosoUniversity.ViewModels
{
    public class AssignedCourseData
    {
        public int CourseID { get; set; }
        public string Title { get; set; }
        public bool Assigned { get; set; }
    }
}

InstructorController.cs中,将HttpGetEdit该方法替换为以下代码。 突出显示所作更改。

public ActionResult Edit(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Instructor instructor = db.Instructors
        .Include(i => i.OfficeAssignment)
        .Include(i => i.Courses)
        .Where(i => i.ID == id)
        .Single();
    if (instructor == null)
    {
        return HttpNotFound();
    }
    PopulateAssignedCourseData(instructor);
    return View(instructor);
}

private void PopulateAssignedCourseData(Instructor instructor)
{
    var allCourses = db.Courses;
    var instructorCourses = new HashSet<int>(instructor.Courses.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)
        });
    }
    ViewBag.Courses = viewModel;
}

该代码为 Courses 导航属性添加了预先加载,并调用新的 PopulateAssignedCourseData 方法使用 AssignedCourseData 视图模型类为复选框数组提供信息。

PopulateAssignedCourseData 方法中的代码会读取所有 Course 实体,以便使用视图模型类加载课程列表。 对每门课程而言,该代码都会检查讲师的 Courses 导航属性中是否存在该课程。 若要在检查是否将课程分配给讲师时创建有效的查找,分配给讲师的课程将放入 HashSet 集合中。 对于分配讲师的课程,该 Assigned 属性设置为 true 。 视图将使用此属性来确定应将哪些复选框显示为选中状态。 最后,列表将传递给属性中的 ViewBag 视图。

接下来,添加用户单击“保存”时执行的代码。 将 EditPost 方法替换为以下代码,该代码调用更新 Courses 实体导航属性 Instructor 的新方法。 突出显示所作更改。

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(int? id, string[] selectedCourses)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    var instructorToUpdate = db.Instructors
       .Include(i => i.OfficeAssignment)
       .Include(i => i.Courses)
       .Where(i => i.ID == id)
       .Single();

    if (TryUpdateModel(instructorToUpdate, "",
       new string[] { "LastName", "FirstMidName", "HireDate", "OfficeAssignment" }))
    {
        try
        {
            if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment.Location))
            {
                instructorToUpdate.OfficeAssignment = null;
            }

            UpdateInstructorCourses(selectedCourses, instructorToUpdate);

            db.SaveChanges();

            return RedirectToAction("Index");
        }
        catch (RetryLimitExceededException /* dex */)
        {
            //Log the error (uncomment dex variable name and add a line here to write a log.
            ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
        }
    }
    PopulateAssignedCourseData(instructorToUpdate);
    return View(instructorToUpdate);
}
private void UpdateInstructorCourses(string[] selectedCourses, Instructor instructorToUpdate)
{
   if (selectedCourses == null)
   {
      instructorToUpdate.Courses = new List<Course>();
      return;
   }
 
   var selectedCoursesHS = new HashSet<string>(selectedCourses);
   var instructorCourses = new HashSet<int>
       (instructorToUpdate.Courses.Select(c => c.CourseID));
   foreach (var course in db.Courses)
   {
      if (selectedCoursesHS.Contains(course.CourseID.ToString()))
      {
         if (!instructorCourses.Contains(course.CourseID))
         {
            instructorToUpdate.Courses.Add(course);
         }
      }
      else
      {
         if (instructorCourses.Contains(course.CourseID))
         {
            instructorToUpdate.Courses.Remove(course);
         }
      }
   }
}

方法签名现在与HttpGetEdit方法不同,因此方法名称会从EditPost回更改为 Edit

由于视图没有实体集合 Course ,因此模型绑定器无法自动更新 Courses 导航属性。 你将在新方法中UpdateInstructorCourses执行此操作,而不是使用模型绑定器来更新Courses导航属性。 为此,需要从模型绑定中排除 Courses 属性。 这不需要对调用 TryUpdateModel 的代码进行任何更改,因为你使用的是显式列表重载, Courses 并且不在包含列表中。

如果未选中复选框,则使用 UpdateInstructorCourses 空集合初始化 Courses 导航属性的代码:

if (selectedCourses == null)
{
    instructorToUpdate.Courses = new List<Course>();
    return;
}

之后,代码会循环访问数据库中的所有课程,并逐一检查当前分配给讲师的课程和视图中处于选中状态的课程。 为便于高效查找,后两个集合存储在 HashSet 对象中。

如果某课程的复选框处于选中状态,但该课程不在 Instructor.Courses 导航属性中,则会将该课程添加到导航属性中的集合中。

if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
    if (!instructorCourses.Contains(course.CourseID))
    {
        instructorToUpdate.Courses.Add(course);
    }
}

如果某课程的复选框未处于选中状态,但该课程存在 Instructor.Courses 导航属性中,则会从导航属性中删除该课程。

else
{
    if (instructorCourses.Contains(course.CourseID))
    {
        instructorToUpdate.Courses.Remove(course);
    }
}

Views\Instructor\Edit.cshtml 中,添加包含复选框数组的 Courses 字段,方法是在字段的元素OfficeAssignmentdiv“保存”按钮的元素之前div立即添加以下代码:

<div class="form-group">
    <div class="col-md-offset-2 col-md-10">
        <table>
            <tr>
                @{
                    int cnt = 0;
                    List<ContosoUniversity.ViewModels.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>

粘贴代码后,如果换行符和缩进看起来不像此处所做的那样,请手动修复所有内容,使其看起来像你在此处看到的内容。 缩进不一定要完美,但 @</tr><tr>@:<td>@:</td>@</tr> 行必须各成一行(如下所示),否则会出现运行时错误。

此代码将创建一个具有三列的 HTML 表。 每个列中都有一个复选框,随后是一段由课程编号和标题组成的描述文字。 复选框都具有相同的名称(“selectedCourses”),这会通知模型联编程序,它们将被视为组。 每个 value 复选框的属性设置为“发布页面时”的值 CourseID. ,模型绑定器会将数组传递给控制器,该控制器仅包含 CourseID 所选复选框的值。

最初呈现复选框时,分配给讲师的课程的复选框具有 checked 属性(选中这些属性)。

更改课程作业后,您需要能够在网站返回到 Index 页面时验证更改。 因此,需要向该页中的表添加列。 在这种情况下,无需使用该 ViewBag 对象,因为要显示的信息已位于 Courses 要作为模型传递给页面的 Instructor 实体的导航属性中。

Views\Instructor\Index.cshtml 中,紧跟 Office 标题添加 Courses 标题,如以下示例所示:

<tr> 
    <th>Last Name</th> 
    <th>First Name</th> 
    <th>Hire Date</th> 
    <th>Office</th>
    <th>Courses</th>
    <th></th> 
</tr>

然后紧跟在办公室位置详细信息单元格后面添加新的详细信息单元格:

<td>
    @if (item.OfficeAssignment != null)
    {
        @item.OfficeAssignment.Location
    }
</td>
<td>
    @{
        foreach (var course in item.Courses)
        {
            @course.CourseID @:  @course.Title <br />
        }
    }
</td>
<td>
    @Html.ActionLink("Select", "Index", new { id = item.ID }) |
    @Html.ActionLink("Edit", "Edit", new { id = item.ID }) |
    @Html.ActionLink("Details", "Details", new { id = item.ID }) |
    @Html.ActionLink("Delete", "Delete", new { id = item.ID })
</td>

运行“讲师索引”页,查看分配给每个讲师的课程。

单击讲师上的“编辑”以查看“编辑”页面。

更改一些课程作业,然后单击“ 保存”。 所作更改将反映在索引页上。

注意:当课程数量有限时,此处用于编辑讲师课程数据的方法非常有效。 若是远大于此的集合,则需要使用不同的 UI 和不同的更新方法。

更新 DeleteConfirmed

InstructorController.cs中,删除 DeleteConfirmed 该方法并插入以下代码。

[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
   Instructor instructor = db.Instructors
     .Include(i => i.OfficeAssignment)
     .Where(i => i.ID == id)
     .Single();

   db.Instructors.Remove(instructor);

    var department = db.Departments
        .Where(d => d.InstructorID == id)
        .SingleOrDefault();
    if (department != null)
    {
        department.InstructorID = null;
    }

   db.SaveChanges();
   return RedirectToAction("Index");
}

此代码进行以下更改:

  • 如果讲师被分配为任何部门的管理员,请从该部门中删除讲师分配。 如果没有此代码,如果尝试删除被分配为部门管理员的讲师,则会收到引用完整性错误。

此代码不处理为多个部门的管理员分配的一名讲师的方案。 在上一篇教程中,你将添加代码来防止这种情况发生。

向创建页添加办公室位置和课程

InstructorController.cs中删除HttpGetHttpPostCreate方法,然后将以下代码添加到其位置:

public ActionResult Create()
{
    var instructor = new Instructor();
    instructor.Courses = new List<Course>();
    PopulateAssignedCourseData(instructor);
    return View();
}

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "LastName,FirstMidName,HireDate,OfficeAssignment" )]Instructor instructor, string[] selectedCourses)
{
    if (selectedCourses != null)
    {
        instructor.Courses = new List<Course>();
        foreach (var course in selectedCourses)
        {
            var courseToAdd = db.Courses.Find(int.Parse(course));
            instructor.Courses.Add(courseToAdd);
        }
    }
    if (ModelState.IsValid)
    {
        db.Instructors.Add(instructor);
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    PopulateAssignedCourseData(instructor);
    return View(instructor);
}

此代码类似于你在编辑方法中看到的内容,但最初未选择任何课程。 该方法HttpGetCreate调用PopulateAssignedCourseData方法不是因为可能选择了课程,而是为了在视图中为循环提供空集合foreach(否则视图代码将引发 null 引用异常)。

HttpPost Create 方法将每个所选课程添加到 Courses 导航属性,然后模板代码检查验证错误并将新讲师添加到数据库。 即使存在模型错误,也会添加课程,以便在存在模型错误时(例如,用户键控无效日期),以便在页面重新显示错误消息时,会自动还原所做的任何课程选择。

请注意,为了能够向 Courses 导航属性添加课程,必须将该属性初始化为空集合:

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

除了在控制器代码中进行此操作之外,还可以在“导师”模型中进行此操作,方法是将该属性更改为不存在集合时自动创建集合,如以下示例所示:

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

如果通过这种方式修改 Courses 属性,则可以删除控制器中的显式属性初始化代码。

Views\Instructor\Create.cshtml 中,在“雇用日期”字段和“提交”按钮之前添加办公室位置文本框和课程复选框。

<div class="form-group">
    @Html.LabelFor(model => model.OfficeAssignment.Location, new { @class = "control-label col-md-2" })
    <div class="col-md-10">
        @Html.EditorFor(model => model.OfficeAssignment.Location)
        @Html.ValidationMessageFor(model => model.OfficeAssignment.Location)
    </div>
</div>

<div class="form-group">
    <div class="col-md-offset-2 col-md-10">
        <table>
            <tr>
                @{
                    int cnt = 0;
                    List<ContosoUniversity.ViewModels.AssignedCourseData> courses = ViewBag.Courses;

                    foreach (var course in courses)
                    {
                        if (cnt++ % 3 == 0)
                        {
                            @:</tr><tr>
                        }
                        @:<td>
                            <input type="checkbox"
                               name="selectedCourses"
                               value="@course.CourseID"
                               @(Html.Raw(course.Assigned ? "checked=\"checked\"" : "")) />
                               @course.CourseID @:  @course.Title
                        @:</td>
                    }
                    @:</tr>
                }
        </table>
    </div>
</div>

粘贴代码后,像之前对“编辑”页面所做的那样修复换行符和缩进。

运行“创建”页并添加讲师。

处理翻译

如基本 CRUD 功能教程中所述,默认情况下,Entity Framework 隐式实现事务。 对于需要更多控制的方案(例如,如果要在事务中包含实体框架外部执行的操作),请参阅 在 MSDN 上使用事务

获取代码

下载已完成的项目

其他资源

可以在 ASP.NET 数据访问 - 建议的资源中找到 指向其他 Entity Framework 资源的链接。

下一步

在本教程中,你将了解:

  • 自定义课程页面
  • 向讲师页面添加了办公室
  • 向讲师页面添加了课程
  • 更新了 DeleteConfirmed
  • 向“创建”页面添加了办公室位置和课程

请继续学习下一篇文章,了解如何实现异步编程模型。