上一篇链接:EF学习笔记(七):读取关联数据

本篇原文链接:Updating Related Data

本篇主要考虑对于有关联的数据进行新增、删除、更新操作;比如Course 、Instructor;

对于Course来说,新增时候必须定义属于哪个Department,所以在新增、更新操作的时候,必须要用户选择Department;

MVC5在选择基础控制器及视图框架的时候,如果选择EF的操作框架,则会自动带一部分基础代码,比如Course的Create\Edit直接就带上了Department的下拉列表选择框;

但是对于一些错误情况处理不够以及没有做显示排序,原文做了些调整优化;

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);
}

PopulateDepartmentsDropDownList方法给视图传递了一个用Name排序过的Department列表;

并且处理了新建的时候 选中的Department为空的情况;

Create、Edit的Post请求中都对异常情况做了处理,并同时在下拉框还保持原先的选择;

另外,在Create、Edit的视图中,对于Department的标题也需要从DepartmentID改为Department :

<div class="form-group">
<label class="control-label col-md-2" for="DepartmentID">Department</label>
@*@Html.LabelFor(model => model.DepartmentID, "DepartmentID", htmlAttributes: new { @class = "control-label col-md-2" })*@
<div class="col-md-10">
@Html.DropDownList("DepartmentID", null, htmlAttributes: new { @class = "form-control" })
@Html.ValidationMessageFor(model => model.DepartmentID, "", new { @class = "text-danger" })
</div>
</div>

另外,对于Create视图,EF默认框架建立的视图会根据主关键字段是不是自动列来采用不同的方式;
如果定义为自动增加列,则不需要用户输入,由数据库自增长;

如果定义为非自动增长列,则需要用户手动,则会提供一个输入框;

但对于Edit视图,EF默认框架建立的视图不会明文显示主关键字段,因为它不知道这个字段到底有没有显示意义,它只用一个隐藏字段来包含主关键字段数据;

所以要手动增加显示主关键字段,但是就算自己手动增加显示主关键字段,也不可以删除那个隐藏字段!

@Html.ValidationSummary(true, "", new { @class = "text-danger" })
@Html.HiddenFor(model => model.CourseID) <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> <div class="form-group">
@Html.LabelFor(model => model.Title, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Title, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.Title, "", new { @class = "text-danger" })
</div>
</div>

同样,在Delete、Detail视图中,需要加入CourseID的显示:

<dt>
@Html.DisplayNameFor(model => model.Department.Name)
</dt>
<dd>
@Html.DisplayFor(model => model.Department.Name)
</dd>
<dt>
@Html.DisplayNameFor(model => model.CourseID)
</dt>
<dd>
@Html.DisplayFor(model => model.CourseID)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Title)
</dt>
<dd>
@Html.DisplayFor(model => model.Title)
</dd>

Course 修改效果:

Instructors 更新

对于Instructors,当进行编辑的时候,需要对Instructors指定Office,同时Instructors对于Office是 1对0或1 的关系,即有可能有Office,有可能没有 Office;

所以对于Instructors,编辑的时候,会碰到3种情况:

1、如果清除了Office,那么需要删除 Instructors和Office的对应关系;

2、如果原来是没有Office的,现在输入了Office,则需要增加 Instructors和Office的对应关系;

3、如果原来就有Office,现在输入了新的Office,则需要变更 Instructors和Office的对应关系;(变更OfficeAssginment实体)

EF框架自动生成的Edit Get请求肯定是不能满足以上要求的;(EF框架给搞了一个下拉框,这根本就不对)

所以把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);
}

直接把Office数据从数据库里查出来;然后准备放在一个编辑框里显示出来;

通过原来的代码中的 find方法是没办法直接把Office数据一起查询出来的,所以需要用Where+Single来完成;

把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);
}

先用和Get请求中一样的代码,把原instructor从数据库里取出;
然后用 TryUpdateModel 来通过设置的列名白名单来更新实体数据;

如果Office输入为空,则设置Instructor的OfficeAssignment导航属性为空,则EF会自动在保存的时候,删除Instructors和Office的对应表的数据行;

在Edit 视图中,增加Office的编辑框:

<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的时候一并建立;

Instructor对应Course是1对0或多,那么只能通过多个CheckBox来选取;原文用的方式确实比较惊奇,在视图中进行拼接显示视图的方式;

首先,先定义个显示Model用来支持Course显示:(CourseID用来定义ID,Title用来显示Course标题,Assigned用来对应CheckBox是否选中)

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

第2步;编辑Instructor Edit Get请求:把Instructor的Course显示Model取出来传给视图;

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;
}

第3步,编辑Instructor Edit Post请求,来进行Instructor更新,在传回InstructorID的同时,传回选中的CourseID列表:

[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);
}
}
}
}

原文的意思是,视图没有Courses的实体集合,所以模型绑定不能自动把instructor的course属性自动绑定上去,只能手动后台进行绑定;
而且,如果没有选择任何course ,也需要初始化一个空的Courses的实体集合;

后面就是做原有的Courses的实体集合和新选择的courseID列表的对比,新增的就Add,去除的就Remove;

第4步,就是要改视图:

在Office 那个<div>下面增加一段:

<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<table>
<tr>
@{
int cnt = ;
List<EFTest.ViewModels.AssignedCourseData> courses = ViewBag.Courses; foreach (var course in courses)
{
if (cnt++ % == )
{
@:</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>

代码确实比较神奇,不过就是直接粘贴上去后,需要调整调整格式,不然格式是乱的。。。刚开始看的时候,确实比较头疼。。。
看看样式结果:

对于Create也一样,需要修改Create Get 请求、Post请求:

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);
}

这里,原文还提到一个优化操作;对于新增加的Instructor,一开始是没有Course列表的,但是要视图正确显示,需要手动new一个Course实例集合;
那么可以考虑把这个动作放在取Course实例集合的时候统一做一次New操作:(在Instructor的Courses的导航属性的get方法中,如果courses为空,则new一个空集合;)

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

有了这个修改,在控制器里的,手动new Course实例集合的一行可以去掉。
最后把Create对应的视图也增加CheckBox来选择Course:

<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 = ;
List<EFTest.ViewModels.AssignedCourseData> courses = ViewBag.Courses; foreach (var course in courses)
{
if (cnt++ % == )
{
@:</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>

最后Create的视图显示结果:

后面来修改Index视图,在Index 视图里直接多行显示出Instructor的Course;

先在标题列增加 Courses标题:

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

然后在Office的后面循环行显示Course:

<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>

最后显示结果:

最后,对于DeleteComfirm 请求也需要修改:

[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");
}

这里的修改主要是对删除instructor后对Department的影响做处理;
不然,直接删除instructor,如果有Department有这个instructor的外键,就会引发异常,需要清除这个Department 对应的instructor .

最后,EF框架默认采用事务的方式处理数据库请求;即要么全部成功,要么全部不成功。

【EF6学习笔记】(八)更新关联数据的更多相关文章

  1. EF学习笔记(八):更新关联数据

    学习笔记主目录链接:ASP.NET MVC5 及 EF6 学习笔记 - (目录整理) 上一篇链接:EF学习笔记(七):读取关联数据 本篇原文链接:Updating Related Data 本篇主要考 ...

  2. 【EF6学习笔记】目录

    [EF6学习笔记](一)Code First 方式生成数据库及初始化数据库实际操作 [EF6学习笔记](二)操练 CRUD 增删改查 [EF6学习笔记](三)排序.过滤查询及分页 [EF6学习笔记]( ...

  3. ASP.NET MVC5 及 EF6 学习笔记 - (目录整理)

    个人从传统的CS应用开发(WPF)开始转向BS架构应用开发: 先是采用了最容易上手也是最容易搞不清楚状况的WebForm方式入手:到后面就直接抛弃了服务器控件的开发方式,转而采用 普通页面+Ajax+ ...

  4. EF6学习笔记(六) 创建复杂的数据模型

    EF6学习笔记总目录:ASP.NET MVC5 及 EF6 学习笔记 - (目录整理) 本篇原文地址:Creating a More Complex Data Model 本篇讲的比较碎,很多内容本人 ...

  5. EF6 学习笔记(二):操练 CRUD 增删改查

    EF6学习笔记总目录 ASP.NET MVC5 及 EF6 学习笔记 - (目录整理) 接上篇: EF6 学习笔记(一):Code First 方式生成数据库及初始化数据库实际操作 本篇原文链接: I ...

  6. Contoso 大学 - 6 – 更新关联数据

    原文 Contoso 大学 - 6 – 更新关联数据 By Tom Dykstra, Tom Dykstra is a Senior Programming Writer on Microsoft's ...

  7. SQL反模式学习笔记7 多态关联

    目标:引用多个父表 反模式:使用多用途外键.这种设计也叫做多态关联,或者杂乱关联. 多态关联和EAV有着相似的特征:元数据对象的名字是存储在字符串中的. 在多态关联中,父表的名字是存储在Issue_T ...

  8. EF6 学习笔记(五):数据库迁移及部署

    EF6学习笔记总目录:ASP.NET MVC5 及 EF6 学习笔记 - (目录整理) 原文地址:Code First Migrations and Deployment 原文主要讲两部分:开发环境下 ...

  9. EF6学习笔记(四) 弹性连接及命令拦截调试

    EF6学习笔记总目录:ASP.NET MVC5 及 EF6 学习笔记 - (目录整理) 本章原文地址:Connection Resiliency and Command Interception 原文 ...

  10. EF6 学习笔记(三):排序、过滤查询及分页

    EF6 学习笔记索引目录页: ASP.NET MVC5 及 EF6 学习笔记 - (目录整理) 上篇:EF6 学习笔记(二):操练 CRUD 增删改查 本篇原文地址:Sorting, Filterin ...

随机推荐

  1. 通过TABULATE过程制作汇总报表

    通过TABULATE过程制作汇总报表 制作基本汇总报表 TABULATE过程的基本语法如下: PROC TABULATE DATA=数据集 <选项>; CLASS 变量1 <变量2变 ...

  2. 2019.03.25 bzoj4567: [Scoi2016]背单词(trie+贪心)

    传送门 题意: 给你n个字符串,不同的排列有不同的代价,代价按照如下方式计算(字符串s的位置为x): 1.排在s后面的字符串有s的后缀,则代价为n^2: 2.排在s前面的字符串有s的后缀,且没有排在s ...

  3. 在JAVA中对于类,对象,继承,多态的看法

    这是我第一次学高级语言.很抱歉选择了JAVA,目标是开发一款可以上线的APP. 类:是建立对象的一个模板,就是系列产品中的基础款图纸.只是图纸而已.不是产品. 对象:是一个可以操作的对象.新建一个对象 ...

  4. PHP与Excel 笔记

    一:   PHP将数据导出Excel表中(投机型) 二: PHPExcel: Github上可以下载此插件包,用法如下: 前端: //上传阅卷员Excel文件 $("#upload_memb ...

  5. PHP PDO预定义常量

    以下常量由本扩展模块定义,因此只有在本扩展的模块被编译到PHP中,或者在运行时被动态加载后才有效. 注意: PDO使用类常量自PHP 5.1.以前的版本使用的全局常量形式PDO_PARAM_BOOL中 ...

  6. Windows多线程学习随笔

    自学Windows多线程知识,例程如下: #include <iostream> #include <windows.h> #include <process.h> ...

  7. 【慕课网实战】七、以慕课网日志分析为例 进入大数据 Spark SQL 的世界

    用户:     方便快速从不同的数据源(json.parquet.rdbms),经过混合处理(json join parquet),     再将处理结果以特定的格式(json.parquet)写回到 ...

  8. 用python实现文件自动上传

    一.简介 用python实现文件自动上传,主要源于在测试项目中想实现自动化上传文件功能,无须手工输入.比如从windows到Linux,或从Linux到windows,或从Linux到Linux. 主 ...

  9. web专业课学习及往后方向发展

    日常10点起床!!!! web主要是网页设计,目前自我方向是学习web前端开发,熟悉掌握相关的编辑应用已达到能设计出满意的网页,日后继续学习后端等 ,成为全栈工程师.

  10. 虚拟机找不到/mnt/hgfs挂载目录——debian与 vmware

    如果在安装好 VMware Tools 并在设置里面设定好共享目录之后仍然找不到 /mnt/hgfs 默认挂载目录,那么尝试以下步骤: 1. 确认VMware Tools 和共享目录设定已经完成: 2 ...