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 ...
随机推荐
- MongoDB-分片
1 分区12 分区23 路由服务器实例-mongos(客户端访问它)4 配置服务器实例-config 1 分片 cd /d D:\Test\bin1 10001 2 分片 cd /d D:\Test\ ...
- C#调用Win32API
Win32API.cs using System;using System.Drawing;using System.Runtime.InteropServices;using Lordal.Wi ...
- nginx android app 慢网络请求超时
最近遇到了android 在慢网络下面请求服务器报 java.net.SocketException: recvfrom failed: ECONNRESET (Connection reset by ...
- 安装windows服务批处理代码
批处理是DOS时代比较常用的方法之一,目前来说也是一种高效的方法,复制代码到文本文件中,保存并修改文件扩展名为“*.bat”. 安装windows服务批处理代码如下: @echo off set fi ...
- PHP 之 CURL 模拟登陆并获取数据
1.CURL模拟登陆的流程和步骤 2.tempnam 创建一个临时文件 3.使用CURL模拟登陆到PHP100论坛 <?php $cookie_file = tempnam('./temp',' ...
- 基于thinkphp的省略图便捷函数
/** * 生成缩略图 * @param string $image 原图路径 例:thumb_5242d9082fcdc.jpg * @param string $type 图像格式 * @para ...
- Devexpress Ribbon
http://www.cnblogs.com/liwei81730/archive/2011/12/21/2296203.html 可查看此处.
- maven报错非法字符:\65279 错误
开发中一个项目很早就报这个错,maven报错非法字符:\65279 错误,今天终于忍无可忍要解决它 :编译java文件的时候,有些java文件报非法字符 \65279错误,在网上找和很多 方法,也试了 ...
- RadioGroup实现导航栏
[声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/ ...
- nodemailer 发邮件
var transporter = nodemailer.createTransport({//v1.0 above do not use 'SMTP' as first param host: &q ...