您的位置:首页 > 其它

EntityFramework_MVC4中EF5 新手入门教程之六 ---6.通过 Entity Framework 更新关联数据

2015-02-05 17:19 525 查看
在前面的教程中,您将显示相关的数据 ;在本教程中,您会更新相关的数据。对于大多数的关系,这个目标是可以通过更新相应的外键字段来达到的。对于多对多关系,实体框架并不直接,暴露联接表,因此您必须显式添加和删除,并从相应的导航属性的实体。

下面的插图显示页面,您将利用工作。





为课程自定义创建和编辑页面

当创建新的课程实体时,它必须拥有一个关系到现行的部门。为了推动这项工作,搭建的代码包括控制器方法,并且创建和编辑视图中包括用于选择处的下拉列表。下拉列表中设置
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>

[code]        <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")
}[/code]
在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= 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); } }
}
}

由于视图没有
Course
实体的集合,模型联编程序不能自动更新
Courses
导航属性。而不是使用模型联编程序更新课程导航属性,你可以做到在新的
UpdateInstructorCourses
方法。因此,您需要将
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,通过添加下面突出显示的代码为
OfficeAssignment
字段
div
元素后立即添加课程领域与数组的复选框:

@model ContosoUniversity.Models.Instructor

@{
ViewBag.Title = "Edit";
}

<h2>Edit</h2>

@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
@Html.ValidationSummary(true)

<fieldset>
<legend>Instructor</legend>

@Html.HiddenFor(model => model.InstructorID)

<div class="editor-label">
@Html.LabelFor(model => model.LastName)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.LastName)
@Html.ValidationMessageFor(model => model.LastName)
</div>

<div class="editor-label">
@Html.LabelFor(model => model.FirstMidName)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.FirstMidName)
@Html.ValidationMessageFor(model => model.FirstMidName)
</div>

<div class="editor-label">
@Html.LabelFor(model => model.HireDate)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.HireDate)
@Html.ValidationMessageFor(model => model.HireDate)
</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>

<div class="editor-field">
<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>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}

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

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

此代码创建一个 HTML 表,其中包含三列。在每一列是其后组成的课程编号和标题的标题的复选框。所有的复选框具有相同的名称 ("selectedCourses"),通知他们将被视为一个组的模型联编程序。每个复选框的
value
属性设置为的值
CourseID.
当该页时,模型联编程序将数组传递给控制器
CourseID
值只选定复选框组成。

复选框最初呈现时,那些分配给教师的课程有
checked
属性,选择它们 (显示他们检查)。

更改后的课程作业,你会想要能够验证所做的更改,当站点返回到
Index
页面。因此,您需要将列添加到该页面中的表。在这种情况下你不需要使用
ViewBag
对象,因为你想要显示的信息已经被你正在传递到页中的模型作为
Instructor
实体的
Courses
导航属性。

在Views\Instructor\Index.cshtml,添加课程标题紧接办公室标题,如下面的示例所示:

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

然后添加新的详细信息单元,紧接着办公室位置详细信息单元:

@model ContosoUniversity.ViewModels.InstructorIndexData

@{
ViewBag.Title = "Instructors";
}

<h2>Instructors</h2>

<p>
@Html.ActionLink("Create New", "Create")
</p>
<table>
<tr> <th></th> <th>Last Name</th> <th>First Name</th> <th>Hire Date</th> <th>Office</th> <th>Courses</th> </tr>
@foreach (var item in Model.Instructors)
{
string selectedRow = "";
if (item.InstructorID == ViewBag.InstructorID)
{
selectedRow = "selectedrow";
}
<tr class="@selectedRow" valign="top">
<td>
@Html.ActionLink("Select", "Index", new { id = item.InstructorID }) |
@Html.ActionLink("Edit", "Edit", new { id = item.InstructorID }) |
@Html.ActionLink("Details", "Details", new { id = item.InstructorID }) |
@Html.ActionLink("Delete", "Delete", new { id = item.InstructorID })
</td>
<td>
@item.LastName
</td>
<td>
@item.FirstMidName
</td>
<td>
@String.Format("{0:d}", item.HireDate)
</td>
<td>
@if (item.OfficeAssignment != null)
{
@item.OfficeAssignment.Location
}
</td>
<td>
@{
foreach (var course in item.Courses)
{
@course.CourseID @: @course.Title <br />
}
}
</td>
</tr>
}
</table>

@if (Model.Courses!= null)
{
<h3>CoursesTaught by Selected Instructor</h3>
<table>
<tr>
<th></th>
<th>ID</th>
<th>Title</th>
<th>Department</th>
</tr>

@foreach (var item in Model.Courses)
{
string selectedRow = "";
if (item.CourseID == ViewBag.CourseID)
{
selectedRow = "selectedrow";
}

<tr class="@selectedRow">

<td>
@Html.ActionLink("Select", "Index", new { courseID = item.CourseID })
</td>
<td>
@item.CourseID
</td>
<td>
@item.Title
</td>
<td>
@item.Department.Name
</td>
</tr>
}

</table>
}
@if (Model.Enrollments != null)
{
<h3>Students Enrolled in Selected Course</h3>
<table>
<tr>
<th>Name</th>
<th>Grade</th>
</tr>
@foreach (var item in Model.Enrollments)
{
<tr>
<td>
@item.Student.FullName
</td>
<td>
@Html.DisplayFor(modelItem => item.Grade)
</td>
</tr>
}
</table>
}

</td>

运行教练索引页后,可以看到分配给每个教师的课程:



指导员看到编辑页上,请单击编辑



更改某些课程作业并单击保存。你所做的更改反映在索引页上。

注: 编辑教练课程数据而采取的做法工作以及为数有限的课程时。对于要大得多的集合,将需要不同的用户界面和不同的更新方法。

更新删除方法

改变 HttpPost 删除方法中的代码,所以教练被删除时删除办公室分配记录 (如果有):

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

instructor.OfficeAssignment = null;
db.Instructors.Remove(instructor);
db.SaveChanges();
return RedirectToAction("Index");
}

如果您尝试删除教练分配到任何部门作为管理员,您将得到一个参照完整性错误。请参阅本教程中的当前版本将自动从任何部门,其中讲师分配作为管理员移除讲师的额外代码。

摘要

现在,您已完成此简介工作的相关数据。到目前为止这些教程中你做过全范围的 CRUD 操作,但是你没有先处理并发问题。下一个教程将并发的话题引入、 解释选项来处理它,并添加到你已经写出了一种实体类型的 CRUD 代码处理的并发。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐