Entity?Framework?Core關(guān)聯(lián)刪除
關(guān)聯(lián)刪除通常是一個(gè)數(shù)據(jù)庫(kù)術(shù)語(yǔ),用于描述在刪除行時(shí)允許自動(dòng)觸發(fā)刪除關(guān)聯(lián)行的特征;即當(dāng)主表的數(shù)據(jù)行被刪除時(shí),自動(dòng)將關(guān)聯(lián)表中依賴的數(shù)據(jù)行進(jìn)行刪除,或者將外鍵更新為NULL或默認(rèn)值。
數(shù)據(jù)庫(kù)關(guān)聯(lián)刪除行為
我們先來(lái)看一看SQL Server中支持的行為。在創(chuàng)建外鍵約束時(shí),可以指定關(guān)聯(lián)表在主表刪除行時(shí),對(duì)依賴的數(shù)據(jù)如何執(zhí)行操作。例如下面的SQL語(yǔ)句,[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]末尾的語(yǔ)句是ON DELETE SET NULL,表示當(dāng)主表的數(shù)據(jù)行刪除時(shí),自動(dòng)將關(guān)聯(lián)表數(shù)據(jù)行的外鍵更新為NULL。
在SQL Server中支持如下四種行為:
1.ON DELETE NO ACTION
默認(rèn)行為,刪除主表數(shù)據(jù)行時(shí),依賴表中的數(shù)據(jù)不會(huì)執(zhí)行任何操作,此時(shí)會(huì)產(chǎn)生錯(cuò)誤,并回滾DELETE語(yǔ)句。例如會(huì)產(chǎn)生下面的錯(cuò)誤:
DELETE 語(yǔ)句與 REFERENCE 約束"FK_Order Details_Orders_OrderID"沖突。該沖突發(fā)生于數(shù)據(jù)庫(kù)"Northwind_Test",表"dbo.Order Details", column 'OrderID'。
語(yǔ)句已終止。
2.ON DELETE CASCADE
刪除主表數(shù)據(jù)行時(shí),依賴表的中數(shù)據(jù)行也會(huì)同步刪除。
3.ON DELETE SET NULL
刪除主表數(shù)據(jù)行時(shí),將依賴表中數(shù)據(jù)行的外鍵更新為NULL。為了滿足此約束,目標(biāo)表的外鍵列必須可為空值。
4.ON DELETE SET DEFAULT
刪除主表數(shù)據(jù)行時(shí),將依賴表的中數(shù)據(jù)行的外鍵更新為默認(rèn)值。為了滿足此約束,目標(biāo)表的所有外鍵列必須具有默認(rèn)值定義;如果外鍵可為空值,并且未顯式設(shè)置默認(rèn)值,則將使用NULL作為該列的隱式默認(rèn)值。
簡(jiǎn)單介紹了數(shù)據(jù)庫(kù)中行為后,我們來(lái)著重介紹 EF Core 中的關(guān)聯(lián)實(shí)體的行為。
定義實(shí)體
我們先定義兩個(gè)實(shí)體Order、OrderDetail分別表示訂單和訂單明細(xì);其中Order與OrderDetail的關(guān)系是一對(duì)多,在OrderDetail實(shí)體中OrderID表示外鍵,依賴于Order實(shí)體中的主鍵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 配置關(guān)聯(lián)實(shí)體
在DbContext中OnModelCreating方法中,我們使用 Fluent API 配置實(shí)體中之間的關(guān)系。
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方法中,需要傳遞參數(shù)DeleteBehavior枚舉,分別有如下四個(gè)值:
public enum DeleteBehavior
{
Cascade,
SetNull,
ClientSetNull,
Restrict
}
這四個(gè)枚舉值的分別表示不同的行為,這也是我們今天的重點(diǎn)。
創(chuàng)建表結(jié)構(gòu)
我們分別使用使用這這個(gè)枚舉值,來(lái)創(chuàng)建數(shù)據(jù)表結(jié)構(gòu)。
[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();
}
}
四個(gè)枚舉值創(chuàng)建表的SQL語(yǔ)句類似如下,唯一區(qū)別在于創(chuàng)建外鍵約束[FK_Order Details_Orders_OrderID]中ON DELETE {}后面的語(yǔ)句。
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
);
四個(gè)枚舉值分別對(duì)應(yīng)的SQL語(yǔ)句如下:

EF Core 關(guān)聯(lián)實(shí)體刪除行為
我們分別通過(guò)枚舉值與是否跟蹤關(guān)聯(lián)實(shí)體,進(jìn)行代碼測(cè)試,測(cè)試代碼如下:
[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);
}
}
}
?
? ? ? ??
總結(jié)
根據(jù)上面的測(cè)試結(jié)果,我們可以出得如下結(jié)論:
DeleteBehavior.Cascade
- 如果關(guān)聯(lián)實(shí)體未被跟蹤,主實(shí)體的狀態(tài)標(biāo)記為刪除,執(zhí)行SaveChage時(shí),在刪除主表的數(shù)據(jù)的同時(shí),通過(guò)數(shù)據(jù)庫(kù)的行為刪除關(guān)聯(lián)表的數(shù)據(jù)行;
- 如果關(guān)聯(lián)實(shí)體已經(jīng)被跟蹤,將主實(shí)體的狀態(tài)標(biāo)記為刪除時(shí),關(guān)聯(lián)實(shí)體的狀態(tài)也會(huì)標(biāo)記為刪除,執(zhí)行SaveChange時(shí),先刪除關(guān)聯(lián)表的數(shù)據(jù)行,然后再刪除主表的數(shù)據(jù)行;
- 外鍵可以設(shè)置非空值、也可以設(shè)置為可為空值;
- 關(guān)聯(lián)實(shí)體可以不被跟蹤。
DeleteBehavior.SetNull
- 如果關(guān)聯(lián)實(shí)體未被跟蹤,主實(shí)體的狀態(tài)標(biāo)記為刪除,執(zhí)行SaveChage時(shí),在刪除主表的數(shù)據(jù)時(shí),通過(guò)數(shù)據(jù)庫(kù)的行為將關(guān)聯(lián)表數(shù)據(jù)行的外鍵更新為NULL,;
- 如果關(guān)聯(lián)實(shí)體已經(jīng)被跟蹤,將主實(shí)體的狀態(tài)標(biāo)記為刪除時(shí),關(guān)聯(lián)實(shí)體的外鍵會(huì)被設(shè)置為null,同時(shí)將關(guān)聯(lián)實(shí)體的狀態(tài)標(biāo)記為修改,執(zhí)行SaveChange時(shí),先更新關(guān)聯(lián)表的數(shù)據(jù)行 ,然后刪除主表的數(shù)據(jù)行;
- 因?yàn)橐獙⑼怄I更新為NULL,所以外鍵必須設(shè)置為可空字段;
- 關(guān)聯(lián)實(shí)體可以不被跟蹤。
DeleteBehavior.ClientSetNull
- 數(shù)據(jù)庫(kù)不會(huì)執(zhí)行任何行為;
- 關(guān)聯(lián)實(shí)體必須被跟蹤,將主實(shí)體的狀態(tài)標(biāo)記為刪除時(shí),關(guān)聯(lián)實(shí)體的外鍵被設(shè)置為null,同時(shí)將關(guān)聯(lián)實(shí)體的狀態(tài)標(biāo)記為修改,執(zhí)行SaveChange時(shí),先更新關(guān)聯(lián)表的數(shù)據(jù)行,然后刪除主表的數(shù)據(jù)行(此時(shí)的行為與DeleteBehavior.SetNull一致);
- 因?yàn)橐獙⑼怄I更新為NULL,所以外鍵必須設(shè)置為可空字段;
- 關(guān)聯(lián)實(shí)體必須被跟蹤,否則保存數(shù)據(jù)時(shí)會(huì)拋出異常。
DeleteBehavior.Restrict
- 框架不執(zhí)行任何操作,由開(kāi)發(fā)人員決定關(guān)聯(lián)實(shí)體的行為,可以將關(guān)聯(lián)實(shí)體的狀態(tài)設(shè)置為刪除,也可以將關(guān)聯(lián)實(shí)體的外鍵設(shè)置為null;
- 因?yàn)橐薷年P(guān)聯(lián)實(shí)體的狀態(tài)或外鍵的值,所以關(guān)聯(lián)實(shí)體必須被跟蹤。?
以上就是Entity Framework Core關(guān)聯(lián)刪除的詳細(xì)內(nèi)容,更多關(guān)于Entity Framework關(guān)聯(lián)刪除的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- Entity Framework Core使用控制臺(tái)程序生成數(shù)據(jù)庫(kù)表
- Entity Framework Core延遲加載(懶加載)用法
- Entity?Framework?Core實(shí)現(xiàn)Like查詢?cè)斀?/a>
- Entity Framework Core中執(zhí)行SQL語(yǔ)句和存儲(chǔ)過(guò)程的方法介紹
- Entity Framework Core批處理SQL語(yǔ)句
- Entity Framework Core實(shí)現(xiàn)軟刪除與查詢過(guò)濾器
- Entity Framework Core生成列并跟蹤列記錄
- ASP.NET Core在WebApi項(xiàng)目中使用MiniProfiler分析Entity Framework Core
- Entity Framework Core工具使用命令行
- 詳解如何在ASP.NET Core中應(yīng)用Entity Framework
- Entity Framework Core對(duì)Web項(xiàng)目生成數(shù)據(jù)庫(kù)表
相關(guān)文章
ASP.NET?Core中Razor頁(yè)面與MVC區(qū)別介紹
這篇文章介紹了ASP.NET?Core中Razor頁(yè)面與MVC的區(qū)別,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-02-02
Asp.Net Core MVC項(xiàng)目實(shí)現(xiàn)多語(yǔ)言實(shí)例(Globalization/Localization)
本篇文章主要介紹了Asp.Net Core MVC項(xiàng)目實(shí)現(xiàn)多語(yǔ)言實(shí)例(Globalization/Localization) ,具有一定的參考價(jià)值,有興趣的可以了解一下2017-06-06
ASP.NET?Core使用Swagger/OpenAPI規(guī)范
這篇文章介紹了ASP.NET?Core使用Swagger/OpenAPI規(guī)范的方法,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04
ASP.NET組件System.Web.Optimization原理及緩存問(wèn)題詳解
這篇文章主要介紹了ASP.NET組件System.Web.Optimization的運(yùn)行原理,及基本的緩存問(wèn)題,感興趣的小伙伴們可以參考一下2016-05-05
.NET/C#如何判斷某個(gè)類是否是泛型類型或泛型接口的子類型詳解
這篇文章主要給大家介紹了關(guān)于.NET/C#如何判斷某個(gè)類是否是泛型類型或泛型接口的子類型的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起看看吧2018-09-09
.net中實(shí)現(xiàn)listBox左右移動(dòng)
這里給大家推薦的是一段網(wǎng)友分享的,使用.net實(shí)現(xiàn)listBox左右移動(dòng)的代碼,簡(jiǎn)單實(shí)用,這里記錄下來(lái),有需要的小伙伴參考下吧。2015-03-03

