EntityFramework_MVC4中EF5 新手入门教程之六 ---6.通过 Entity Framework 更新关联数据
在前面的教程中,您将显示相关的数据 ;在本教程中,您会更新相关的数据。对于大多数的关系,这个目标是可以通过更新相应的外键字段来达到的。对于多对多关系,实体框架并不直接,暴露联接表,因此您必须显式添加和删除,并从相应的导航属性的实体。
下面的插图显示页面,您将利用工作。
为课程自定义创建和编辑页面
当创建新的课程实体时,它必须拥有一个关系到现行的部门。为了推动这项工作,搭建的代码包括控制器方法,并且创建和编辑视图中包括用于选择处的下拉列表。下拉列表中设置Course.DepartmentID
的外键属性,,这是所有实体框架所需加载具有适当的Department
实体的Department
导航属性。您可以使用搭建的代码,但改变它微微地添加错误处理和下拉列表进行排序。
在CourseController.cs,删除四个Edit
和Create
方法并替换为以下代码:
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 (DataException /* dex */)
{
//Log the error (uncomment dex variable name after DataException 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)
{
Course course = db.Courses.Find(id);
PopulateDepartmentsDropDownList(course.DepartmentID);
return View(course);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(
[Bind(Include = "CourseID,Title,Credits,DepartmentID")]
Course course)
{
try
{
if (ModelState.IsValid)
{
db.Entry(course).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
}
catch (DataException /* dex */)
{
//Log the error (uncomment dex variable name after DataException 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);
}
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);
}
PopulateDepartmentsDropDownList
方法获取列表按名称排序的所有部门、 创建下拉列表,SelectList
的集合并将集合传递到ViewBag
属性中查看。该方法接受可选的selectedDepartment
参数允许调用代码来指定当呈现下拉列表中将选定的项。视图会将DepartmentID
的名称传递给DropDownList
helper,和助手就会知道应该命名为DepartmentID
的SelectList
ViewBag
对象中寻找.
HttpGet
Create
方法调用PopulateDepartmentsDropDownList
方法无需设置所选的项目,因为有一个新课程部尚未确立:
public ActionResult Create()
{
PopulateDepartmentsDropDownList();
return View();
}
HttpGet
Edit
方法设置所选的项目,基于已分配给课程正在编辑该署的 ID:
public ActionResult Edit(int id)
{
Course course = db.Courses.Find(id);
PopulateDepartmentsDropDownList(course.DepartmentID);
return View(course);
}
Create
和Edit
的HttpPost
方法还包括设置选定的项,当他们出现错误之后重新显示该页的代码:
catch (DataException /* dex */)
{
//Log the error (uncomment dex variable name after DataException 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);
此代码可确保当要显示错误消息,重新显示页上,不管部入选始终保持选定状态。
在Views\Course\Create.cshtml,添加突出显示的代码,以创建一个新的课程编号字段在标题字段之前。正如解释在早些时候的教程中,主键字段不搭建在默认情况下,但此主键是有意义的所以您希望用户能够输入的密钥值。
@model ContosoUniversity.Models.Course
@{
ViewBag.Title = "Create";
}
<h2>Create</h2>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
@Html.ValidationSummary(true)
<fieldset>
<legend>Course</legend>
<div class="editor-label">
@Html.LabelFor(model => model.CourseID)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.CourseID)
@Html.ValidationMessageFor(model => model.CourseID)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Title)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Title)
@Html.ValidationMessageFor(model => model.Title)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Credits)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Credits)
@Html.ValidationMessageFor(model => model.Credits)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.DepartmentID, "Department")
</div>
<div class="editor-field">
@Html.DropDownList("DepartmentID", String.Empty)
@Html.ValidationMessageFor(model => model.DepartmentID)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}
在Views\Course\Edit.cshtml、 Views\Course\Delete.cshtml、 Views\Course\Details.cshtml添加课程数字字段在Title字段之前。因为它是主键,它会显示,但不能更改。
<div class="editor-label">
@Html.LabelFor(model => model.CourseID)
</div>
<div class="editor-field">
@Html.DisplayFor(model => model.CourseID)
</div>
运行创建页 (显示路线索引页面,并单击新建) 和输入一门新课程的数据:
单击创建。课程索引页面显示添加到列表中的新课程。在索引页面列表中的部门名称来自于导航属性,显示正确建立了关系。
运行编辑页 (显示路线索引页面,并在课程上单击编辑)。
更改页面上的数据并单击保存。课程索引页显示已更新的课程数据。
添加Instructors编辑页
当您编辑一个教练记录时,你想要能够更新教师的办公室分配。Instructor
实体具有一个到零或一个关联OfficeAssignment
实体,这就意味着你必须处理以下情况:
- 如果用户清除办公室分配,它原本的价值,您必须删除并删除
OfficeAssignment
实体。 - 如果用户输入的办公室分配值,它最初为空,您必须创建一个新的
OfficeAssignment
实体。 - 如果用户更改办公室被分配的值,则必须更改现有的
OfficeAssignment
实体中的值。
打开InstructorController.cs ,然后看看HttpGet
Edit
方法:
public ActionResult Edit(int id = 0)
{
Instructor instructor = db.Instructors.Find(id);
if (instructor == null)
{
return HttpNotFound();
}
ViewBag.InstructorID = new SelectList(db.OfficeAssignments, "InstructorID", "Location", instructor.InstructorID);
return View(instructor);
}
这里搭建的代码不是你想要什么。它设置数据为下拉列表,但你你所需要的是一个文本框。此方法替换为以下代码:
public ActionResult Edit(int id)
{
Instructor instructor = db.Instructors
.Include(i => i.OfficeAssignment)
.Where(i => i.InstructorID == id)
.Single();
return View(instructor);
}
这段代码滴ViewBag
声明,并添加预先加载关联的OfficeAssignment
实体。所以相反的Where
和Single
方法用于选择教练的情况下,不能使用Find
方法,执行预先加载。
用以下代码替换HttpPost
Edit
方法。处理办公室工作分配更新:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(int id, FormCollection formCollection)
{
var instructorToUpdate = db.Instructors
.Include(i => i.OfficeAssignment)
.Where(i => i.InstructorID == id)
.Single();
if (TryUpdateModel(instructorToUpdate, "",
new string[] { "LastName", "FirstMidName", "HireDate", "OfficeAssignment" }))
{
try
{
if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment.Location))
{
instructorToUpdate.OfficeAssignment = null;
}
db.Entry(instructorToUpdate).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
catch (DataException /* dex */)
{
//Log the error (uncomment dex variable name after DataException 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.");
}
}
ViewBag.InstructorID = new SelectList(db.OfficeAssignments, "InstructorID", "Location", id);
return View(instructorToUpdate);
}
该代码执行以下任务:
-
获取从数据库使用的当前的
Instructor
实体预先加载OfficeAssignment
导航属性。这是你的所作所为中HttpGet
Edit
方法相同。 -
从模型联编程序的值更新检索到的
Instructor
实体。您想要包括的属性使用的TryUpdateModel重载使您到白名单中。这可以防止过度张贴,如所述第二个教程.if (TryUpdateModel(instructorToUpdate, "", new string[] { "LastName", "FirstMidName", "HireDate", "OfficeAssignment" }))
-
如果办公室位置为空,将设置
Instructor.OfficeAssignment
属性,以便将删除OfficeAssignment
表中的相关的行,则为 null。if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment.Location)) { instructorToUpdate.OfficeAssignment = null; }
- 将更改保存到数据库中。
在Views\Instructor\Edit.cshtml后div
元素为雇佣日期字段中,, 添加新的字段编辑办公地点:
<div class="editor-label">
@Html.LabelFor(model => model.OfficeAssignment.Location)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.OfficeAssignment.Location)
@Html.ValidationMessageFor(model => model.OfficeAssignment.Location)
</div>
运行该页面 (选择教官选项卡,然后点击编辑讲师)。更改办公地点并单击保存.
在指导老师编辑页添加课程作业
教练可以教任何数目的课程。现在您将通过添加更改课程作业使用一组复选框,如下面的屏幕快照中所示的能力增强导师编辑页面:
Course
和Instructor
实体之间的关系是多,这意味着您没有直接访问到联接表。相反,您将添加和删除实体,从Instructor.Courses
导航属性。
用户界面中,使您能够更改哪些课程的讲师是分配给是一组复选框。显示数据库中的每一门课程的复选框,并选择了那些讲师当前分配给。用户可以选择或清除复选框以更改课程作业。如果课程数目大得多,你可能想要使用不同的方法,在视图中,显示数据,但您将使用相同的方法操纵导航属性以创建或删除关系。
为列表中的复选框提供数据到视图,您将使用一个视图模型类。在Viewmodel文件夹中创建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,用下面的代码替换HttpGet
Edit
方法。高亮行为所做的更改。
public ActionResult Edit(int id)
{
Instructor instructor = db.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.Courses)
.Where(i => i.InstructorID == id)
.Single();
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
属性中的视图。
接下来,添加用户单击保存时执行的代码。HttpPost
Edit
方法替换以下代码中,调用更新Instructor
实体的Courses
导航属性的新方法。突出显示所做的更改。
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(int id, FormCollection formCollection, string[] selectedCourses)
{
var instructorToUpdate = db.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.Courses)
.Where(i => i.InstructorID == 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.Entry(instructorToUpdate).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
catch (DataException /* dex */)
{
//Log the error (uncomment dex variable name after DataException 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