关联删除通常是一个数据库术语,用于描述在删除行时允许自动触发删除关联行的特征;即当主表的数据行被删除时,自动将关联表中依赖的数据行进行删除,或者将外键更新为NULL或默认值。

数据库关联删除行为

我们先来看一看SQL Server中支持的行为。在创建外键约束时,可以指定关联表在主表删除行时,对依赖的数据如何执行操作。例如下面的SQL语句,[Order Details]表中[OrderID]字段 是外键,依赖于[Orders]表中的主键[OrderID]。

CREATE TABLE [Orders] (
[OrderID] int NOT NULL IDENTITY,
[Name] nvarchar(max) NULL,
[OrderDate] datetime2 NULL,
CONSTRAINT [PK_Orders] PRIMARY KEY ([OrderID])
); GO CREATE TABLE [Order Details] (
[DetailId] int NOT NULL IDENTITY,
[OrderID] int NULL,
[ProductID] int NOT NULL,
CONSTRAINT [PK_Order Details] PRIMARY KEY ([DetailId]),
CONSTRAINT [FK_Order Details_Orders_OrderID] FOREIGN KEY ([OrderID]) REFERENCES [Orders] ([OrderID]) ON DELETE SET NULL
);

外键约束[FK_Order Details_Orders_OrderID]末尾的语句是ON DELETE SET NULL,表示当主表的数据行删除时,自动将关联表数据行的外键更新为NULL。

在SQL Server中支持如下四种行为:

  • ON DELETE NO ACTION

    默认行为,删除主表数据行时,依赖表中的数据不会执行任何操作,此时会产生错误,并回滚DELETE语句。例如会产生下面的错误:

    DELETE 语句与 REFERENCE 约束"FK_Order Details_Orders_OrderID"冲突。该冲突发生于数据库"Northwind_Test",表"dbo.Order Details", column 'OrderID'。

    语句已终止。

  • ON DELETE CASCADE

    删除主表数据行时,依赖表的中数据行也会同步删除。
  • ON DELETE SET NULL

    删除主表数据行时,将依赖表中数据行的外键更新为NULL。为了满足此约束,目标表的外键列必须可为空值。
  • ON DELETE SET DEFAULT

    删除主表数据行时,将依赖表的中数据行的外键更新为默认值。为了满足此约束,目标表的所有外键列必须具有默认值定义;如果外键可为空值,并且未显式设置默认值,则将使用NULL作为该列的隐式默认值。

简单介绍了数据库中行为后,我们来着重介绍 EF Core 中的关联实体的行为。

定义实体

我们先定义两个实体Order、OrderDetail分别表示订单和订单明细;其中Order与OrderDetail的关系是一对多,在OrderDetail实体中OrderID表示外键,依赖于Order实体中的主键OrderID。

    public class Order
{
public int OrderID { get; set; } public string Name { get; set; } public DateTime? OrderDate { get; set; } public ICollection<OrderDetail> OrderDetails { get; set; }
} public class OrderDetail
{
public int DetailId { get; set; } public int? OrderID { get; set; } public int ProductID { get; set; } public Order Order { get; set; }
}

Fluent API 配置关联实体

在DbContext中OnModelCreating方法中,我们使用 Fluent API 配置实体中之间的关系。

    public class NorthwindContext : DbContext
{ public virtual DbSet<Order> Orders { get; set; }
public virtual DbSet<OrderDetail> OrderDetails { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>(
builder =>
{
builder.HasMany<OrderDetail>(e => e.OrderDetails).WithOne(e => e.Order).HasForeignKey(e => e.OrderID).OnDelete(DeleteBehavior.ClientSetNull);
});
}
}

在OnDelete方法中,需要传递参数DeleteBehavior枚举,分别有如下四个值:

    public enum DeleteBehavior
{
Cascade, SetNull, ClientSetNull, Restrict
}

这四个枚举值的分别表示不同的行为,这也是我们今天的重点。

创建表结构

我们分别使用使用这这个枚举值,来创建数据表结构。


[InlineData(DeleteBehavior.Cascade)]
[InlineData(DeleteBehavior.SetNull)]
[InlineData(DeleteBehavior.ClientSetNull)]
[InlineData(DeleteBehavior.Restrict)]
[Theory]
public void Create_Database(DeleteBehavior behavior)
{
using (var northwindContext = new NorthwindContext(behavior))
{
northwindContext.Database.EnsureDeleted();
northwindContext.Database.EnsureCreated();
}
}

四个枚举值创建表的SQL语句类似如下,唯一区别在于创建外键约束[FK_Order Details_Orders_OrderID]中ON DELETE {} 后面的语句。


CREATE TABLE [Orders] (
[OrderID] int NOT NULL IDENTITY,
[Name] nvarchar(max) NULL,
[OrderDate] datetime2 NULL,
CONSTRAINT [PK_Orders] PRIMARY KEY ([OrderID])
); GO CREATE TABLE [Order Details] (
[DetailId] int NOT NULL IDENTITY,
[OrderID] int NOT NULL,
[ProductID] int NOT NULL,
CONSTRAINT [PK_Order Details] PRIMARY KEY ([DetailId]),
CONSTRAINT [FK_Order Details_Orders_OrderID] FOREIGN KEY ([OrderID]) REFERENCES [Orders] ([OrderID]) ON DELETE CASCADE
);

四个枚举值分别对应的SQL语句如下:

枚举值 SQL语句
DeleteBehavior.Cascade ON DELETE CASCADE
DeleteBehavior.SetNull ON DELETE SET NULL
DeleteBehavior.ClientSetNull ON DELETE NO ACTION
DeleteBehavior.Restrict ON DELETE NO ACTION

EF Core 关联实体删除行为

我们分别通过枚举值与是否跟踪关联实体,进行代码测试,测试代码如下:


[InlineData(DeleteBehavior.Cascade, true)]
[InlineData(DeleteBehavior.Cascade, false)]
[InlineData(DeleteBehavior.SetNull, true)]
[InlineData(DeleteBehavior.SetNull, false)]
[InlineData(DeleteBehavior.ClientSetNull, true)]
[InlineData(DeleteBehavior.ClientSetNull, false)]
[InlineData(DeleteBehavior.Restrict, true)]
[InlineData(DeleteBehavior.Restrict, false)] [Theory]
public void Execute(DeleteBehavior behavior, bool includeDetail)
{
using (var northwindContext = new NorthwindContext(behavior))
{
northwindContext.Database.EnsureDeleted();
northwindContext.Database.EnsureCreated();
} int orderId;
int detailId;
using (var northwindContext = new NorthwindContext(behavior))
{
var order = new Order {
Name = "Order1"
}; var orderDetail = new OrderDetail {
ProductID = 11
};
order.OrderDetails = new List<OrderDetail> {
orderDetail
}; northwindContext.Set<Order>().Add(order);
northwindContext.SaveChanges(); orderId = order.OrderID;
detailId = orderDetail.DetailId;
} using (var northwindContext = new NorthwindContext(behavior))
{
var queryable = northwindContext.Set<Order>().Where(e => e.OrderID == orderId);
if (includeDetail){
queryable = queryable.Include(e => e.OrderDetails);
} var order = queryable.Single();
northwindContext.Set<Order>().Remove(order); try
{
northwindContext.SaveChanges();
DumpSql();
}
catch (Exception)
{
DumpSql();
throw;
} } using (var northwindContext = new NorthwindContext(behavior))
{
var orderDetail = northwindContext.Set<OrderDetail>().Find(detailId);
if (behavior == DeleteBehavior.Cascade)
{
Assert.Null(orderDetail);
}
else
{
Assert.NotNull(orderDetail);
}
}
}
枚举值 是否跟踪关联实体 是否成功调用SaveChange 关联实体是否存在 执行的SQL
DeleteBehavior.Cascade No 成功 否 DELETE FROM [Orders] WHERE [OrderID] = 1
DeleteBehavior.Cascade YES 成功 否 DELETE FROM [Order Details] WHERE [DetailId] = 1
DELETE FROM [Orders] WHERE [OrderID] = 1
DeleteBehavior.SetNull No 成功 YES
(外键为NULL)
DELETE FROM [Orders] WHERE [OrderID] = 1
DeleteBehavior.SetNull YES 成功 YES
(外键为NULL)
UPDATE [Order Details] SET [OrderID] = NULL WHERE [DetailId] = 1
DELETE FROM [Orders] WHERE [OrderID] = 1
DeleteBehavior.ClientSetNull No 失败
(外键约束)
YES DELETE FROM [Orders] WHERE [OrderID] = 1
DeleteBehavior.ClientSetNull YES 成功 YES
(外键为NULL)
UPDATE [Order Details] SET [OrderID] = NULL WHERE [DetailId] = 1
DELETE FROM [Orders] WHERE [OrderID] = 1
DeleteBehavior.Restrict No 失败
(外键约束)
YES DELETE FROM [Orders] WHERE [OrderID] = 1
DeleteBehavior.Restrict YES 失败
(外键约束)
YES DELETE FROM [Orders] WHERE [OrderID] = 1

总结

根据上面的测试结果,我们可以出得如下结论:

DeleteBehavior.Cascade

  • 如果关联实体未被跟踪,主实体的状态标记为删除,执行SaveChage时,在删除主表的数据的同时,通过数据库的行为删除关联表的数据行;
  • 如果关联实体已经被跟踪,将主实体的状态标记为删除时,关联实体的状态也会标记为删除,执行SaveChange时,先删除关联表的数据行,然后再删除主表的数据行;
  • 外键可以设置非空值、也可以设置为可为空值;
  • 关联实体可以不被跟踪。

DeleteBehavior.SetNull

  • 如果关联实体未被跟踪,主实体的状态标记为删除,执行SaveChage时,在删除主表的数据时,通过数据库的行为将关联表数据行的外键更新为NULL,;
  • 如果关联实体已经被跟踪,将主实体的状态标记为删除时,关联实体的外键会被设置为null,同时将关联实体的状态标记为修改,执行SaveChange时,先更新关联表的数据行 ,然后删除主表的数据行;
  • 因为要将外键更新为NULL,所以外键必须设置为可空字段;
  • 关联实体可以不被跟踪。

DeleteBehavior.ClientSetNull

  • 数据库不会执行任何行为;
  • 关联实体必须被跟踪,将主实体的状态标记为删除时,关联实体的外键被设置为null,同时将关联实体的状态标记为修改,执行SaveChange时,先更新关联表的数据行,然后删除主表的数据行(此时的行为与DeleteBehavior.SetNull一致);
  • 因为要将外键更新为NULL,所以外键必须设置为可空字段;
  • 关联实体必须被跟踪,否则保存数据时会抛出异常。

DeleteBehavior.Restrict

  • 框架不执行任何操作,由开发人员决定关联实体的行为,可以将关联实体的状态设置为删除,也可以将关联实体的外键设置为null;
  • 因为要修改关联实体的状态或外键的值,所以关联实体必须被跟踪。

Entity Framework Core 关联删除的更多相关文章

  1. Entity Framework Core 软删除与查询过滤器

    本文翻译自<Entity Framework Core: Soft Delete using Query Filters>,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 注意 ...

  2. 【EF】Entity Framework Core 软删除与查询过滤器

    本文翻译自<Entity Framework Core: Soft Delete using Query Filters>,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 注意 ...

  3. Entity Framework Core系列之DbContext(删除)

    上一篇我们介绍了Entity Framework Core系列之DbContext(修改),这一篇我们介绍下删除数据 修改实体的方法取决于context是否正在跟踪需要删除的实体. 下面的示例中con ...

  4. 全自动迁移数据库的实现 (Fluent NHibernate, Entity Framework Core)

    在开发涉及到数据库的程序时,常会遇到一开始设计的结构不能满足需求需要再添加新字段或新表的情况,这时就需要进行数据库迁移. 实现数据库迁移有很多种办法,从手动管理各个版本的ddl脚本,到实现自己的mig ...

  5. Working with Data » Getting started with ASP.NET Core and Entity Framework Core using Visual Studio » 创建复杂数据模型

    Creating a complex data model 创建复杂数据模型 8 of 9 people found this helpful The Contoso University sampl ...

  6. Working with Data » 使用Visual Studio开发ASP.NET Core MVC and Entity Framework Core初学者教程

    原文地址:https://docs.asp.net/en/latest/data/ef-mvc/intro.html The Contoso University sample web applica ...

  7. .Net Core 2.0生态(4):Entity Framework Core 2.0 特性介绍和使用指南

    前言 这是.Net Core 2.0生态生态介绍的最后一篇,EF一直是我喜欢的一个ORM框架,随着版本升级EF也发展到EF6.x,Entity Framework Core是一个支持跨平台的全新版本, ...

  8. Entity Framework Core 2.0 入门简介

    不多说废话了, 直接切入正题. EF Core支持情况 EF Core的数据库Providers: 此外还即将支持CosmosDB和 Oracle. EFCore 2.0新的东西: 查询: EF.Fu ...

  9. Entity Framework Core 2.0 入门

    该文章比较基础, 不多说废话了, 直接切入正题. 该文分以下几点: 创建Model和数据库 使用Model与数据库交互 查询和保存关联数据 EF Core支持情况 EF Core的数据库Provide ...

随机推荐

  1. Python--简单接口测试实例(一)

    适用人员:初学python的测试人员,若对抓包不太清楚的可先学习抓包的知识 接口测试流程:发送请求-->返回响应-->结果判定-->生成报告 案例:下面以[今目标]新建客户为例来进行 ...

  2. 重装完的win10卡“请稍等”,然后电脑不断自动重启还是卡在“请稍等”?

    http://bbs.pcbeta.com/viewthread-1625778-1-1.html 说实话我用U盘安装win10遇到过几次这种情况,不管是win10正式版还是之前的个别win10测试版 ...

  3. [开源]基于ffmpeg和libvlc的视频剪辑、播放器

    [开源]基于ffmpeg和libvlc的视频剪辑.播放器 以前研究的时候,写过一个简单的基于VLC的视频播放器.后来因为各种项目,有时为了方便测试,等各种原因,陆续加了一些功能,现在集成了视频播放.视 ...

  4. pycharm linux版快捷方式创建

    ****************************pycharm_linux安装and快捷方式创建******************1.下载好安装包之后解压:    tar -xfz 压缩包名 ...

  5. redis主从相关问题

    redis主从是如何实现同步的 第一次.Slave向Master同步的实现是:        Slave向Master发出同步请求(发送sync命令),Master先dump出rdb文件,然后将rdb ...

  6. SSM-SpringMVC-25:SpringMVC异常顶级之自定义异常解析器

    ------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- 上篇博客相信大家也看到了,自定义异常,用了SimpleMappingExceptionResolver这个解析 ...

  7. SSM-Spring-17:Spring中aspectJ注解版

    ------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- AspectJ AspectJ是一个面向切面的框架,它扩展了Java语言,定义了AOP 语法,能够在编译期提供 ...

  8. Windows Server 2008取消登录前的Ctrl+Alt+Delete组合键操作

    前言: 在Windows Server 2008服务器中,为了防止人们登录服务器时错误的将账户和密码输入其他地方导致信息泄漏,所以在我们登录Windows Server 2008服务器操作系统时会要求 ...

  9. RestTemplate 微信接口 text/plain HttpMessageConverter

    一.背景介绍 使用 Spring Boot 写项目,需要用到微信接口获取用户信息. 在 Jessey 和 Spring RestTemplate 两个 Rest 客户端中,想到尽量不引入更多的东西,然 ...

  10. 安装vmtools之后任然不能在虚拟机和主机之间复制粘贴的问题

    安装vmtools之后任然不能在虚拟机和主机之间复制粘贴的问题 都是因为这个进程没有启动起来,你只需要在启动后在终端输入 "/usr/bin /vmware-user" 就可以手动 ...