【EF6学习笔记】(八)更新关联数据
上一篇链接:EF学习笔记(七):读取关联数据
本篇原文链接:Updating Related Data
本篇主要考虑对于有关联的数据进行新增、删除、更新操作;比如Course 、Instructor;
对于Course来说,新增时候必须定义属于哪个Department,所以在新增、更新操作的时候,必须要用户选择Department;
MVC5在选择基础控制器及视图框架的时候,如果选择EF的操作框架,则会自动带一部分基础代码,比如Course的Create\Edit直接就带上了Department的下拉列表选择框;
但是对于一些错误情况处理不够以及没有做显示排序,原文做了些调整优化;
![](http://common.cnblogs.com/images/copycode.gif)
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);
}
![](http://common.cnblogs.com/images/copycode.gif)
PopulateDepartmentsDropDownList
方法给视图传递了一个用Name排序过的Department列表;
并且处理了新建的时候 选中的Department为空的情况;
Create、Edit的Post请求中都对异常情况做了处理,并同时在下拉框还保持原先的选择;
另外,在Create、Edit的视图中,对于Department的标题也需要从DepartmentID改为Department :
![](http://common.cnblogs.com/images/copycode.gif)
<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>
![](http://common.cnblogs.com/images/copycode.gif)
另外,对于Create视图,EF默认框架建立的视图会根据主关键字段是不是自动列来采用不同的方式;
如果定义为自动增加列,则不需要用户输入,由数据库自增长;
如果定义为非自动增长列,则需要用户手动,则会提供一个输入框;
但对于Edit视图,EF默认框架建立的视图不会明文显示主关键字段,因为它不知道这个字段到底有没有显示意义,它只用一个隐藏字段来包含主关键字段数据;
所以要手动增加显示主关键字段,但是就算自己手动增加显示主关键字段,也不可以删除那个隐藏字段!
![](http://common.cnblogs.com/images/copycode.gif)
@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>
![](http://common.cnblogs.com/images/copycode.gif)
同样,在Delete、Detail视图中,需要加入CourseID的显示:
![](http://common.cnblogs.com/images/copycode.gif)
<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>
![](http://common.cnblogs.com/images/copycode.gif)
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请求修改为以下方式:
![](http://common.cnblogs.com/images/copycode.gif)
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);
}
![](http://common.cnblogs.com/images/copycode.gif)
直接把Office数据从数据库里查出来;然后准备放在一个编辑框里显示出来;
通过原来的代码中的 find方法是没办法直接把Office数据一起查询出来的,所以需要用Where+Single来完成;
把Edit Post 方法改为如下代码:
![](http://common.cnblogs.com/images/copycode.gif)
[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);
}
![](http://common.cnblogs.com/images/copycode.gif)
先用和Get请求中一样的代码,把原instructor从数据库里取出;
然后用 TryUpdateModel 来通过设置的列名白名单来更新实体数据;
如果Office输入为空,则设置Instructor的OfficeAssignment导航属性为空,则EF会自动在保存的时候,删除Instructors和Office的对应表的数据行;
在Edit 视图中,增加Office的编辑框:
![](http://common.cnblogs.com/images/copycode.gif)
<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>
![](http://common.cnblogs.com/images/copycode.gif)
下面就是准备把Instructor和Course的对应关系在编辑Instructor的时候一并建立;
Instructor对应Course是1对0或多,那么只能通过多个CheckBox来选取;原文用的方式确实比较惊奇,在视图中进行拼接显示视图的方式;
首先,先定义个显示Model用来支持Course显示:(CourseID用来定义ID,Title用来显示Course标题,Assigned用来对应CheckBox是否选中)
![](http://common.cnblogs.com/images/copycode.gif)
namespace EFTest.ViewModels
{
public class AssignedCourseData
{
public int CourseID { get; set; }
public string Title { get; set; }
public bool Assigned { get; set; }
}
}
![](http://common.cnblogs.com/images/copycode.gif)
第2步;编辑Instructor Edit Get请求:把Instructor的Course显示Model取出来传给视图;
![](http://common.cnblogs.com/images/copycode.gif)
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;
}
![](http://common.cnblogs.com/images/copycode.gif)
第3步,编辑Instructor Edit Post请求,来进行Instructor更新,在传回InstructorID的同时,传回选中的CourseID列表:
![](http://common.cnblogs.com/images/copycode.gif)
[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);
}
}
}
}
![](http://common.cnblogs.com/images/copycode.gif)
原文的意思是,视图没有Courses
的实体集合,所以模型绑定不能自动把instructor的course属性自动绑定上去,只能手动后台进行绑定;
而且,如果没有选择任何course ,也需要初始化一个空的Courses
的实体集合;
后面就是做原有的Courses
的实体集合和新选择的courseID列表的对比,新增的就Add,去除的就Remove;
第4步,就是要改视图:
在Office 那个<div>下面增加一段:
![](http://common.cnblogs.com/images/copycode.gif)
<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>
![](http://common.cnblogs.com/images/copycode.gif)
代码确实比较神奇,不过就是直接粘贴上去后,需要调整调整格式,不然格式是乱的。。。刚开始看的时候,确实比较头疼。。。
看看样式结果:
对于Create也一样,需要修改Create Get 请求、Post请求:
![](http://common.cnblogs.com/images/copycode.gif)
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);
}
![](http://common.cnblogs.com/images/copycode.gif)
这里,原文还提到一个优化操作;对于新增加的Instructor,一开始是没有Course列表的,但是要视图正确显示,需要手动new一个Course实例集合;
那么可以考虑把这个动作放在取Course实例集合的时候统一做一次New操作:(在Instructor的Courses的导航属性的get方法中,如果courses为空,则new一个空集合;)
![](http://common.cnblogs.com/images/copycode.gif)
private ICollection<Course> _courses;
public virtual ICollection<Course> Courses
{
get
{
return _courses ?? (_courses = new List<Course>());
}
set
{
_courses = value;
}
}
![](http://common.cnblogs.com/images/copycode.gif)
有了这个修改,在控制器里的,手动new Course实例集合的一行可以去掉。
最后把Create对应的视图也增加CheckBox来选择Course:
![](http://common.cnblogs.com/images/copycode.gif)
<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>
![](http://common.cnblogs.com/images/copycode.gif)
最后Create的视图显示结果:
后面来修改Index视图,在Index 视图里直接多行显示出Instructor的Course;
先在标题列增加 Courses标题:
![](http://common.cnblogs.com/images/copycode.gif)
<tr>
<th>Last Name</th>
<th>First Name</th>
<th>Hire Date</th>
<th>Office</th>
<th>Courses</th>
<th></th>
</tr>
![](http://common.cnblogs.com/images/copycode.gif)
然后在Office的后面循环行显示Course:
![](http://common.cnblogs.com/images/copycode.gif)
<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>
![](http://common.cnblogs.com/images/copycode.gif)
最后显示结果:
最后,对于DeleteComfirm 请求也需要修改:
![](http://common.cnblogs.com/images/copycode.gif)
[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");
}
![](http://common.cnblogs.com/images/copycode.gif)
这里的修改主要是对删除instructor后对Department的影响做处理;
不然,直接删除instructor,如果有Department有这个instructor的外键,就会引发异常,需要清除这个Department 对应的instructor .
最后,EF框架默认采用事务的方式处理数据库请求;即要么全部成功,要么全部不成功。
【EF6学习笔记】(八)更新关联数据的更多相关文章
- EF学习笔记(八):更新关联数据
学习笔记主目录链接:ASP.NET MVC5 及 EF6 学习笔记 - (目录整理) 上一篇链接:EF学习笔记(七):读取关联数据 本篇原文链接:Updating Related Data 本篇主要考 ...
- 【EF6学习笔记】目录
[EF6学习笔记](一)Code First 方式生成数据库及初始化数据库实际操作 [EF6学习笔记](二)操练 CRUD 增删改查 [EF6学习笔记](三)排序.过滤查询及分页 [EF6学习笔记]( ...
- ASP.NET MVC5 及 EF6 学习笔记 - (目录整理)
个人从传统的CS应用开发(WPF)开始转向BS架构应用开发: 先是采用了最容易上手也是最容易搞不清楚状况的WebForm方式入手:到后面就直接抛弃了服务器控件的开发方式,转而采用 普通页面+Ajax+ ...
- EF6学习笔记(六) 创建复杂的数据模型
EF6学习笔记总目录:ASP.NET MVC5 及 EF6 学习笔记 - (目录整理) 本篇原文地址:Creating a More Complex Data Model 本篇讲的比较碎,很多内容本人 ...
- EF6 学习笔记(二):操练 CRUD 增删改查
EF6学习笔记总目录 ASP.NET MVC5 及 EF6 学习笔记 - (目录整理) 接上篇: EF6 学习笔记(一):Code First 方式生成数据库及初始化数据库实际操作 本篇原文链接: I ...
- Contoso 大学 - 6 – 更新关联数据
原文 Contoso 大学 - 6 – 更新关联数据 By Tom Dykstra, Tom Dykstra is a Senior Programming Writer on Microsoft's ...
- SQL反模式学习笔记7 多态关联
目标:引用多个父表 反模式:使用多用途外键.这种设计也叫做多态关联,或者杂乱关联. 多态关联和EAV有着相似的特征:元数据对象的名字是存储在字符串中的. 在多态关联中,父表的名字是存储在Issue_T ...
- EF6 学习笔记(五):数据库迁移及部署
EF6学习笔记总目录:ASP.NET MVC5 及 EF6 学习笔记 - (目录整理) 原文地址:Code First Migrations and Deployment 原文主要讲两部分:开发环境下 ...
- EF6学习笔记(四) 弹性连接及命令拦截调试
EF6学习笔记总目录:ASP.NET MVC5 及 EF6 学习笔记 - (目录整理) 本章原文地址:Connection Resiliency and Command Interception 原文 ...
- EF6 学习笔记(三):排序、过滤查询及分页
EF6 学习笔记索引目录页: ASP.NET MVC5 及 EF6 学习笔记 - (目录整理) 上篇:EF6 学习笔记(二):操练 CRUD 增删改查 本篇原文地址:Sorting, Filterin ...
随机推荐
- HTTP协议 与 TCP协议 的区别
TCP协议是传输层协议,主要解决数据如何在网络中传输,而HTTP是应用层协议,主要解决如何包装数据. TCP/IP和HTTP协议的关系,从本质上来说,二者没有可比性,我们在传输数据时,可以只使用(传输 ...
- Java中十六进制转换 Integer.toHexString()
为了显示一个byte型的单字节十六进制(两位十六进制表示)的编码,请使用: Integer.toHexString((byteVar & 0x000000FF) | 0xFFFFFF00).s ...
- Machine learning | 机器学习中的范数正则化
目录 1. \(l_0\)范数和\(l_1\)范数 2. \(l_2\)范数 3. 核范数(nuclear norm) 参考文献 使用正则化有两大目标: 抑制过拟合: 将先验知识融入学习过程,比如稀疏 ...
- spring bean 注入
概念 http://developer.51cto.com/art/200610/33311.htm http://kb.cnblogs.com/page/45266/ ==https://www.c ...
- Breathe me
Help, I have done it again 帮帮我,我又做错了. I have been here many times before 哪怕这已经不是一两次了. Hurt myself ag ...
- webpack简单教程
1.初始化 安装node后,新建一个目录,比如html5.cmd中切到当前文件夹. npm init -y 这个命令会创建一个默认的package.json.它包含了项目的一些配置参数,通过它可以进行 ...
- idea jetty 配置
一.jetty 网址下载地:https://www.eclipse.org/jetty/ 1.如下图红色箭头--> 点击Downloads 下载 2.选择最新版 .ZIP 下载 3.选择安装路径 ...
- DataOutputStream and DataInputStream
1.在io包中,提供了两个与平台无关的数据操作流 数据输出流(DataOutputStream) 数据输入流(DataInputStream) 2.通常数据输出流会按照一定的格式将数据输出,再通过数据 ...
- Oracle ebs 数据脱敏
https://blog.csdn.net/pan_tian/article/details/16120351Data Masking可对数据进行不可逆的去身份化后,再用于非生产环境,同时自动保留引用 ...
- 算法与数据结构(一) 线性表的顺序存储与链式存储(Swift版)
温故而知新,在接下来的几篇博客中,将会系统的对数据结构的相关内容进行回顾并总结.数据结构乃编程的基础呢,还是要不时拿出来翻一翻回顾一下.当然数据结构相关博客中我们以Swift语言来实现.因为Swift ...