一、概述
Entity Framework Core(EF Core)表示使用外键的关系。具有外键的实体是关系中的子实体或依赖实体。此实体的外键值必须与相关主体/父实体的主键值(或替换键值)匹配。
如果删除主体/父实体,则依赖项/子项的外键值将不再匹配任何主体/父实体的主键或替换键。 这是无效状态,将导致在大多数数据库中出现引用约束冲突。
可通过两种方法来避免此引用约束冲突:
- 将外键值设置为 null。
- 同时删除依赖实体/子实体
第一个选项仅适用于其中外键属性(及其映射到的数据库列)必须可为null的可选关系。
第二个选项适用于任何类型的关系,它被称作"级联删除"。
二、发生级联行为时
当依赖实体/子实体无法再与其当前主体/父实体关联时,需要执行级联删除。发生这种情况的原因可能是主体/父实体已被删除。或者当主体/父实体仍存在,但依赖实体/子实体在再与其关联时。
2.1/删除主体/父实体
本文中使用的实体类
public class Blog { public int Id{get;set;} public string NameP{get;set;} public IList<Post> Posts{get;}=new List<Post>(); } public class Post { public int Id { get; set; } public string Title { get; set; } public string Content { get; set; } public int BlogId { get; set; } public Blog Blog { get; set; } }
其中Blog是与Post(依赖实体/子实体)的关系中的主体/父实体。Post.BlogId是一个外键属性,其值必须与该文章所属博客中的Blog.Id主键匹配。
按照约定,由于Post.BlogId外键属性是不可为null的,因此该关系被配置为必需的。默认情况下,所需的关系配置为使用级联删除。
删除博客时,所有文章都将被级联删除。
using var context = new BlogsContext(); var blog = context.Blogs.OrderBy(e=>.Name).Include(e => e.Posts).First(); context.Remove(blog); context.SaveChanges();
SaveChanges 以 SQL Server 为例,生成以下 SQL:
CommandTimeout='30'] SET NOCOUNT ON; DELETE FROM [Posts] WHERE [Id] = @p0; SELECT @@ROWCOUNT; CommandTimeout='30'] SET NOCOUNT ON; DELETE FROM [Posts] WHERE [Id] = @p0; SELECT @@ROWCOUNT; CommandTimeout='30'] SET NOCOUNT ON; DELETE FROM [Blogs] WHERE [Id] = @p1; SELECT @@ROWCOUNT;
2.2/断开关系
如果不删除博客,而是断开每篇文章与其博客之间的关系。为此,可将每篇文章的引用导航Post.Blog
设置为null:
using var context= new BlogsContext(); var blog = context.Blogs.OrderBy(e => e.Name).Include(e => e.Posts).First(); foreach(var post in blog.Posts) { post.Blog = null; } context.SaveChanges();
还可通过从 Blog.Posts
集合导航中删除每篇文章内容来断开关系:
using var context = new BlogsContext(); var blog = context.Blogs.OrderBy(e => e.Name).Include(e.Posts).First(); blog.Posts.Clear(); context.SaveChanges();
无论哪种情况,结果都一样:没有删除博客,但是删除了不再与任何博客关联的文章:
SET NOCOUNT ON; DELETE FROM [Posts] WHERE [Id] = @p0; SELECT @@ROWCOUNT; SET NOCOUNT ON; DELETE FROM [Posts] WHERE [Id] = @p0; SELECT @@ROWCOUNT;
删除不再与任何主体/依赖实体关联的实体这一行为被称作“删除孤立项”。
级联删除和删除孤立项时密切相关的。当断开与所需的主体/父实体之间的关系时,两者都将导致删除依赖实体/子实体。对于级联删除,由于主体/父实体本身已删除,因此发生了这种断开。对于孤立项,主体/父实体仍然存在,但不再与依赖实体/子实体相关。
三、发生级联行为的位置
可将级联行为应用于:
- 当前DbContext跟踪的实体
- 数据库中尚未加载到上下文中的实体
3.1/级联删除被跟踪实体
EF Core 始终将配置的级联行为应用于跟踪的实体。 这意味着如上面的示例所示,如果应用程序将所有相关的依赖实体/子实体加载到 DbContext 中,则无论如何配置数据库,都将正确应用级联行为。
3.2/数据库中的级联删除
许多数据库系统还提供在数据库中删除实体时触发的级联行为。使用EnsureCreated
或EF Core迁移创建数据库时,EF Core会根据EF Core模型中的级联删除行为来配置这些行为。
CREATE TABLE [Posts] ( [Id] int NOT NULL IDENTITY, [Title] nvarchar(max) NULL, [Content] nvarchar(max) NULL, [BlogId] int NOT NULL, CONSTRAINT [PK_Posts] PRIMARY KEY ([Id]), CONSTRAINT [FK_Posts_Blogs_BlogId] FOREIGN KEY ([BlogId]) REFERENCES [Blogs] ([Id]) ON DELETE CASCADE );
请注意,定义博客和文章之间关系的外键约束是用 ON DELETE CASCADE
配置的。
如果我们知道数据库是这样配置的,那么我们可以删除博客,而无需先加载文章,数据库将负责删除与此博客相关的所有文章。 例如:
using var context = new BlogsContext(); var blog = context.Blogs.OrderBy(e => e.Name).First(); context.Remove(blog); context.SaveChanges();
四、级联NULL
可选关系将可为 null 的外键属性映射到可为 null 的数据库列。 这意味着当删除当前主体/父实体或断开与依赖实体/子实体的关系时,可将外键值设置为 NULL。
让我们再看一下发生级联行为时的示例,但这次可选关系由可为null的Post.BlogId
外键属性表示:
public int? BlogId { get; set; }
删除每篇文章的相关博客时,该文章的外键属性将设置为 NULL。 例如,此代码与之前的代码相同:
using var context = new BlogsContext(); var blog = context.Blogs.OrderBy(e => e.Name).Include(e => e.Posts).First(); context.Remove(blog); context.SaveChanges();
当删除可选关系中的主体/父实体时,数据库也可配置为级联 NULL。 但是,与在数据库中使用级联删除相比,这种情况要少得多。