EntityFramework之孩子删除(四)(你以为你真的懂了?)
前言
从表面去看待事物视线总有点被层层薄雾笼罩的感觉,当你静下心来思考并让指尖飞梭于键盘之上,终将会拨开浓雾见青天。这是我切身体验。
在EF关系配置中,我暂且将主体对象称作为父亲,而依赖对象称作为孩子,父亲与孩子关联的关系可能是必须的也可能是可选的,如果是必须的那么意味着孩子不能因没有父亲而独立存在,又如果父亲被删除了(即父亲与孩子的关系被隔离),那么孩子将变成留守儿童(即孤儿),所以当处在这种情况下时,那么孩子应该需要自动被删除。
话题
必须关系和可选关系
我们接下来就父亲与孩子的关联关系来进行删除的话题。
我们建立三个类,一个类是Student(学生类),一个类是Grade(成绩类),最后一个类是Flower(小红花类)。我们假设有如下场景:一个学生对应多门成绩,但一门成绩就属于一个学生,同时可能学生团队合作表现好,一朵小红花对应多个学生,但是这个小红花肯定只会被一个学生拿走也就只对应一个学生,也有可能没得到小红花。鉴于此,类建立如下:
public class Student /*学生类*/
{
public int Id { get; set; }
public string Name { get; set; } public int? FlowerId { get; set; }
public virtual Flower Flower { get; set; } public virtual ICollection<Grade> Grades { get; set; }
} public class Grade /*成绩类*/
{
public int Id { get; set; } public int Fraction { get; set; } /*学生成绩*/ public int StudentId { get; set; } public virtual Student Student { get; set; }
} public class Flower /*小红花类*/
{
public int Id { get; set; }
public string Remark { get; set; } /*小红花描述*/ public virtual ICollection<Student> Students { get; set; }
}
通过上述描述,我们对应的映射如下:
学生映射:
public class StudentMap : EntityTypeConfiguration<Student>
{
public StudentMap()
{
ToTable("Student");
HasKey(key => key.Id);
HasOptional(p => p.Flower).WithMany(p => p.Students).HasForeignKey(p => p.FlowerId); } }
成绩映射:
public class GradeMap: EntityTypeConfiguration<Grade>
{
public GradeMap()
{
ToTable("Grade");
HasKey(p => p.Id);
HasRequired(p => p.Student).WithMany(p => p.Grades).HasForeignKey(p => p.StudentId);
}
}
对于EF上下文建立,不再描述,不明白的话可以参见我前两篇文章。
对于我们上面的可选字段FlowerId生成数据库中也是可选的,如下:

我们插入数据如图:

现在我们进行如下操作:删除学生姓名为bob的
using (var ctx = new EntityDbContext())
{
ctx.Set<Student>().Remove(ctx.Set<Student>().Single(p => p.Name == "bob"));
}
删除后结果如下:

那么问题来了,为什么我删除学生名为bob的而相关成绩也删除了呢?
答案是在学生和成绩之间建立了一个级联删除,所以会自动进行删除,级联删除也就是当父亲被删除时,其孩子也会被删除,EF Code First为什么会这样做呢?因为学生和成绩之间的关系是必须(Required)的。
EF Code First不仅在实体在进行了配置而且在数据库中进行了配置,因为那是至关重要的,如果级联删除存在于实体中,那么在数据库中也应该必须存在,如果这两者不能同步那么在数据库中会出现约束错误。
接下来我们通过Flower(小花)来简介删除学生姓名为bob的,因为其对应的Remark是so bad(坏学生):
ctx.Set<Flower>().Remove(ctx.Set<Flower>().Include(p => p.Students).Single(p => p.Remark == "so bad"));
结果如下:

那么问题来了,为什么没有删除学生bob呢?
答案就是外键属性FlowerId和导航属性Flower被设置成了空,所以学生bob不会被删除,因为EF Code First不会为可选的关系设置级联删除。
【注意】在此种情况下, 如果你加载学生集合列表到内存中,那么EF Code First会在保存之前将外键属性设置为空。
如果此时你想在可选关系上强制执行删除那就在映射中进行如下操作:
HasOptional(p => p.Flower).WithMany(p => p.Students).HasForeignKey(p => p.FlowerId).WillCascadeOnDelete(true);
接下来如果我进行学生与小花之间的映射进行如下修改:
HasRequired(p => p.Flower).WithMany(p => p.Students).HasForeignKey(p => p.FlowerId);
此时再来进行上述删除: ctx.Set<Flower>().Remove(ctx.Set<Flower>().Include(p => p.Students).Single(p => p.Remark == "so bad")); 此时结果如下:

那么问题来了,为什么这样就能进行相应学生的删除了呢?
答案就是当你用上必须的关系(即Required)之后即使你设置外键属性可为空,但是当映射到数据库之后,它会将其映射为非空的外键字段(可以理解为关系映射比POCO实体手动设置优先级高)!不信看如下图:

小结
(1)当关系为可选(Optional)时此时外键属性和导航属性为空,不会进行级联删除,但是可以用 WillCascadeOnDelete 进行强制删除。
(2)当关系为必须(Required)时此时会内置进行级联删除即使外键属性为可空的类型,也就是说无需多此一举加上WillCascadeOnDelete来进行级联删除。
你是不是觉得关于删除就这么简单呢?那你就大错特错了,请继续看下文。
隔离关系
依然以上述为例,我们现在想象有这样一场景,bob的成绩太差每次都没及格,并且虽给了小红花但是评语写着so bad,这样放学回家如何向爸妈交代呢,至少将成绩考好点吧,于是它要求老师删除他不良的成绩并给其100分的好成绩。在此场景下,我们代码如下:
using (var ctx = new EntityDbContext())
{
var stu = ctx.Set<Student>().Single(p => p.Name == "bob"); stu.Grades.Remove(stu.Grades.OrderBy(p => p.Id).First(p => p.Student.Name == "bob")); stu.Grades.Add(new Grade() { Fraction = });
}
但结果是老师也是有心无力啊,出错了,如下:

因为成绩从导航属性集合中移出后,它变成孤立对象(外键为NULL),提交时,是因为外键约束而失败,异常提示,也显示外键不能为空!
所以此时我们能想到的办法就是直接将孩子进行删除或者通过重写SaveChanges找到并删除。
于是在保存之前我添加如下代码:
ctx.Set<Grade>().Local.Where(p => p.Student == null).ToList().ForEach(r => ctx.Set<Grade>().Remove(r));
重写SaveChanges
public override int SaveChanges()
{
ctx.Set<Grade>()
.Local
.Where(p => p.Student == null).ToList()
.ForEach(r => ctx.Set<Grade>().Remove(r));
return base.SaveChanges();
}
最后通过,数据成功进行添加,如图:

上述代码有如下四点意思
(1)使用DbSet.Local来访问当前通过上下文追踪的没有运行任何数据库查询并且未被删除的成绩实体
(2)过滤列表中每一个没有引用学生实体的数据
(3)通过一个过滤列表的副本,来避免枚举时修改一个Collection
(4)标记每个孤儿(成绩)为已删除
小结
(1)默认情况下,EF Code First认为空的外键属性其关系是可选的,而对于非空的外键属性其关系是必须的。必须关系同时配置了级联删除,以至于如果父亲被删除则其所有的孩子也将被删除。
(2)必须和可选的关系自然能通过Fluent API来进行改变或者Data Anotaions和级联删除能够用Fluent API来进行配置
(3)如果父亲已经被隔离,那么通过级联删除不会删除孩子。
EF 那些琐事儿
上述异常信息被EF团队称作为“概念上可空消息”,因为当一个关系被隔离,则其关系中的外键将被设置为空。然而,如果属性为非空,那么EF在概念上将其设置为空,但是实际上没这么做,所以“概念上可空消息”没有被保存到数据库中而是在异常中。
EntityFramework之孩子删除(四)(你以为你真的懂了?)的更多相关文章
- “三次握手,四次挥手”你真的懂吗?TCP
“三次握手,四次挥手”你真的懂吗? mp.weixin.qq.com 来源:码农桃花源 解读:“拼多多”被薅的问题出在哪儿?损失将如何买单? 之前有推过一篇不错的干货<TCP之三次握手四次挥手 ...
- C#语言————拼接、插入、替换、删除四种方法
StringBuilder sb = new StringBuilder("hello"); sb.Append("world");//拼接 sb.Insert ...
- Springboot的 get查看,post创建,put更新,delete删除 -四种请求实例(form + controller)
总结 --get查看数据, post创建新数据行, put更新数据, delete删除数据行-- add和select功能都共用这一个页面, 需要进行区分显示 ,使用thymeleaf的三元选择,判断 ...
- 11-MySQL-Ubuntu-数据表中数据的删除(四)
数据的删除(delete) (1)物理删除(不可逆,公司不会采取这种方法,如现在一般不会出现注销,数据具有无限价值) 删除整张表的数据!!! delete from 表名; 删除部分给定条件的数据: ...
- mvc core2.1 Identity.EntityFramework Core 实例配置 (四)
https://docs.microsoft.com/zh-cn/aspnet/core/security/authentication/customize_identity_model?view=a ...
- 真的懂了:TCP协议中的三次握手和四次挥手(关闭连接时, 当收到对方的FIN报文时, 仅仅表示对方不在发送数据了, 但是还能接收数据, 己方也未必全部数据都发送对方了。相当于一开始还没接上话不要紧,后来接上话以后得让人把话讲完)
一.TCP报文格式 下面是TCP报文格式图: (1) 序号, Seq(Sequence number), 占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记. (2) 确 ...
- TCP 基础知识
参考 朱小厮-一文详解TCP 博客园-"三次握手,四次挥手"你真的懂吗? 博客园-深度解密HTTP通信细节
- 那些Java架构师必知必会的技术
Java基础 Java 7 和 Java 8 中的 HashMap原理解析 Java7 和 Java8 中的 ConcurrentHashMap 原理解析 Java中自定义注解 Java函数式编程和l ...
- 传输层上的TCP和UDP
参考: 知乎 传输层概述 “三次握手,四次挥手”你真的懂吗? 传输层上的TCP和UDP TCP/IP协议是一个协议簇.里面包括很多协议的,UDP只是其中的一个, 之所以命名为TCP/IP协议,因为TC ...
随机推荐
- 【尺取】POJ 3320
POJ 3320 Jessica's Reading Problem 题意:一本书P页,第i页有ai知识点,问你至少从某一处开始连续要翻多少页才能复习完所有的知识点,不能跨页翻. 思路:<挑战程 ...
- 移动web前端下拉刷新效果
直接复制粘贴 放在页面中即可 <script> window.onload = function(){ window.addEventListener('touchstart', touc ...
- problem-eclipse创建maven项目报错
Could not calculate build plan: Plugin org.apache.maven.plugins:maven-resources-plugin:2.5 or one of ...
- ACCEPTANCE CRITERIA FOR USER STORIES
One of the teams I have recently coached quickly got a grasp of how to phrase user stories but found ...
- host Object和native Object的区别
Native Object: JavaScript语言提供的不依赖于执行宿主的对象,其中一些是内建对象,如:Global.Math:一些是在脚本运行环境中创建来使用的,如:Array.Boolean. ...
- BCD码和十六进制,十进制转换
参考文档: http://wenku.baidu.com/link?url=CfK2Wl7sCEmpzEabnbHSbcwf2t4yoSH6_n8sUIRw54piWaRB7hZ6RkaStWEkbC ...
- Smart3D系列教程1之《浅谈无人机倾斜摄影建模的原理与方法》
一.引言 倾斜摄影测量技术是国际测绘遥感领域近年发展起来的一项高新技术,以大范围.高精度.高清晰的方式全面感知复杂场景,通过高效的数据采集设备及专业的数据处理流程生成的数据成果直观反映地物的外观.位置 ...
- Mimikatz 使用Tips
1.记录 Mimikatz输出: C:\>mimikatz.exe ""privilege::debug"" ""log sekurl ...
- jquery 回到顶部,简洁大方
效果
- 三、jquery操作DOM
DOM(Document Object Model, 文档对象模型)为文档提供了一种结构化的表示方法,通过该方法可以改变文档的内容和展示形式.在实际运用中,DOM更像是桥梁,通过它可以实现跨平台.跨语 ...