原文:Updating Related Data with the Entity Framework in an ASP.NET MVC Application
1.定制Course的Create和Edit页面:
修改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); }
修改Views\Course\Create.cshtml和Views\Course\Edit.cshtml的Departmetn显示名:
<label class="control-label col-md-2" for="DepartmentID">Department</label>
正常情况下,脚手架不会显示主键,因为主键的值由数据库产生并且不可改变并且它的显示对用户无意义。但是在Course实体的Create页面会有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>
修改Views\Course\Delete.cshtml和Views\Course\Details.cshtml:
<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>
运行程序:
2.给Instructor添加Edit页面:
修改Edit的GET方法:
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); }
修改过Edit的POST方法:
[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); }
修改Views\Instructor\Edit.cshtml,在Hire Date后添加:
<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>
运行:
3.为Instructor的Edit页面添加分配Course:
在ViewModels文件夹新建AssignedCourseData.cs:
public class AssignedCourseData { public int CourseID { get; set; } public string Title { get; set; } public bool Assigned { get; set; } }
修改InstructorController.cs中Edit的GET:
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(); PopulateAssignedCourseData(instructor); if (instructor == null) { return HttpNotFound(); } 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; }
修改InstructorController.cs中Edit的POST:
[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); } } } }
修改Views\Instructor\Edit.cshtml,在Save按钮之前添加:
<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>
行必须在同一行,否则会报运行错误。
修改Views\Instructor\Index.cshtml:
<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>
运行:
4.修改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"); }
5.给InstructorController.cs的Create页面添加office location和course:
修改Create方法:
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); }
注意,为了能够给Course导航属性添加course,必须初始化属性为空的集合:
instructor.Courses = new List<Course>();
为了达到这个目的,我们也可以这样写:
private ICollection<Course> _courses; public virtual ICollection<Course> Courses { get { return _courses ?? (_courses = new List<Course>()); } set { _courses = value; } }
修改Views\Instructor\Create.cshtml,在Submit按钮之前添加:
<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>
粘贴代码后,也要像Edit页面一样调整代码。
运行:
6.事务:
我们在之前的教程中提到过,EF隐式实现了事务。如果我们需要更多的控制,例如如果我们想要把在EF之外完成的操作包含在事务中,请参考:Working with Transactions。