前言

从表面去看待事物视线总有点被层层薄雾笼罩的感觉,当你静下心来思考并让指尖飞梭于键盘之上,终将会拨开浓雾见青天。这是我切身体验。

在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之孩子删除(四)(你以为你真的懂了?)的更多相关文章

  1. “三次握手,四次挥手”你真的懂吗?TCP

    “三次握手,四次挥手”你真的懂吗?  mp.weixin.qq.com 来源:码农桃花源 解读:“拼多多”被薅的问题出在哪儿?损失将如何买单? 之前有推过一篇不错的干货<TCP之三次握手四次挥手 ...

  2. C#语言————拼接、插入、替换、删除四种方法

    StringBuilder sb = new StringBuilder("hello"); sb.Append("world");//拼接 sb.Insert ...

  3. Springboot的 get查看,post创建,put更新,delete删除 -四种请求实例(form + controller)

    总结 --get查看数据, post创建新数据行, put更新数据, delete删除数据行-- add和select功能都共用这一个页面, 需要进行区分显示 ,使用thymeleaf的三元选择,判断 ...

  4. 11-MySQL-Ubuntu-数据表中数据的删除(四)

    数据的删除(delete) (1)物理删除(不可逆,公司不会采取这种方法,如现在一般不会出现注销,数据具有无限价值) 删除整张表的数据!!! delete from 表名; 删除部分给定条件的数据: ...

  5. mvc core2.1 Identity.EntityFramework Core 实例配置 (四)

    https://docs.microsoft.com/zh-cn/aspnet/core/security/authentication/customize_identity_model?view=a ...

  6. 真的懂了:TCP协议中的三次握手和四次挥手(关闭连接时, 当收到对方的FIN报文时, 仅仅表示对方不在发送数据了, 但是还能接收数据, 己方也未必全部数据都发送对方了。相当于一开始还没接上话不要紧,后来接上话以后得让人把话讲完)

    一.TCP报文格式 下面是TCP报文格式图: (1) 序号, Seq(Sequence number), 占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记. (2) 确 ...

  7. TCP 基础知识

    参考 朱小厮-一文详解TCP 博客园-"三次握手,四次挥手"你真的懂吗? 博客园-深度解密HTTP通信细节

  8. 那些Java架构师必知必会的技术

    Java基础 Java 7 和 Java 8 中的 HashMap原理解析 Java7 和 Java8 中的 ConcurrentHashMap 原理解析 Java中自定义注解 Java函数式编程和l ...

  9. 传输层上的TCP和UDP

    参考: 知乎 传输层概述 “三次握手,四次挥手”你真的懂吗? 传输层上的TCP和UDP TCP/IP协议是一个协议簇.里面包括很多协议的,UDP只是其中的一个, 之所以命名为TCP/IP协议,因为TC ...

随机推荐

  1. Python3.5 Day1作业:实现用户密码登录,输错三次锁定。

    作业需求: 1.输入用户名密码 2.认证成功后显示欢迎信息 3.输错三次后锁定 实现思路: 1.判断用户是否在黑名单,如果在黑名单提示账号锁定. 2.判断用户是否存在,如果不存在提示账号不存在. 3. ...

  2. 【hihoCoder】1148:2月29日

    问题:http://hihocoder.com/problemset/problem/1148 给定两个日期,计算这两个日期之间有多少个2月29日(包括起始日期). 思路: 1. 将问题转换成求两个日 ...

  3. 纯CSS3制作超级漂亮又实用的加载小图标

    先来一张各种效果的预览图,其实代码并不复杂,关键看自己如何运用. <!doctype html> <head> <meta charset="utf-8&quo ...

  4. Javascript初学篇章_8(事件)

    事件 HTML 事件是发生在 HTML 元素上的事情.例如用户点击按钮时,点击也是一个事件.事件可以用于处理表单验证,用户输入,用户行为及浏览器动作,如: 页面加载时触发事件 页面关闭时触发事件 用户 ...

  5. 部署Thomas Kyte 的 runstats 工具

    runstats是由Thomas Kyte开发的脚本,该脚本能对做同一件事的两个不同方法进行比较,得出孰优孰劣的结果. 1.授权 SQL> grant select on v_$statname ...

  6. 线上应用bug跟踪查找-友盟统计

    线上的应用只要用心点点都能发现些bug,连微信,QQ也不列外.但是bug中最严重的算是闪退了,这导致了用户直接不能使用我们的app. 我们公司是特别注重用户反馈和体验的,我们会定期打电话咨询用户的使用 ...

  7. 5.Powershell变量

    在指令执行过程中,会有一些数据产生,这些数据被用于以后的语句,需要一个存储单元暂时的存放这些数据,这个时候定义一个变量来存储数据.例如$string = “Hello Powershell!” Pow ...

  8. PowerDesigner PDM生成sql脚本时:表的名称和表里面的字段名称都有引号解决。。。

    PowerDesigner PDM生成sql脚本时:表的名称和表里面的字段名称都有引号解决... 1.当你的PowerDesigner 是新安装时,你得设置可能就会出现一些问题,在这里比如:PDM生成 ...

  9. 安卓初級教程(5):TabHost的思考

    package com.myhost; import android.os.Bundle; import android.view.LayoutInflater; import android.wid ...

  10. SQL Server 备份迁移策略

    标签:SQL SERVER/MSSQL SERVER/数据库/DBA/xp_cmdshell/备份压缩 概述 当备份空间不是很充裕的情况下需要找方法将备份文件拷贝到专用的备份机器上去,特别是存储空间不 ...