深入理解 EF Core:使用查詢過濾器實(shí)現(xiàn)數(shù)據(jù)軟刪除
閱讀本文大概需要 15 分鐘。
原文:https://bit.ly/2Cy3J5f
作者:Jon P Smith
翻譯:王亮
聲明:我翻譯技術(shù)文章不是逐句翻譯的,而是根據(jù)我自己的理解來表述的。其中可能會去除一些本人實(shí)在不知道如何組織但又不影響理解的句子。

這篇文章是關(guān)于如何使用 EF Core 實(shí)現(xiàn)軟刪除的,即表面上刪除了數(shù)據(jù),但數(shù)據(jù)并沒有被物理刪除,在需要的時(shí)候你還是可以把它讀取出來的。軟刪除有很多好處,但也有一些值得注意的問題。這篇文章會教你使用 EF Core 實(shí)現(xiàn)一般的軟刪除和復(fù)雜的級聯(lián)軟刪除。在此過程中,我還會介紹如何編寫可重用代碼來提高軟刪除解決方案的開發(fā)效率。
我假設(shè)你對 EF Core 已經(jīng)有了一定的認(rèn)識。但在真正講軟刪除實(shí)現(xiàn)的方案之前,我們先來了解一下如何使用 EF Core 實(shí)現(xiàn)刪除和軟刪除的一些基本知識。
本文是“深入理解 EF Core”系列中的第三篇。以下是本系列文章列表:
深入理解 EF Core:當(dāng) EF Core 從數(shù)據(jù)庫讀取數(shù)據(jù)時(shí)發(fā)生了什么?
深入理解 EF Core:當(dāng) EF Core 寫入數(shù)據(jù)到數(shù)據(jù)庫時(shí)發(fā)生了什么?
深入理解 EF Core:使用查詢過濾器實(shí)現(xiàn)數(shù)據(jù)軟刪除(本文)
概要
∮. 你可以使用全局查詢過濾器(現(xiàn)在稱為查詢過濾器)為你的 EF Core 應(yīng)用程序添加軟刪除功能。
∮. 在應(yīng)用程序中使用軟刪除的主要好處是可以恢復(fù)無意的刪除和保留歷史記錄。
∮. 在應(yīng)用程序中添加軟刪除功能包含以下三個(gè)部分:
向每個(gè)想要軟刪除的實(shí)體類添加一個(gè)新的軟刪除屬性。
在應(yīng)用程序的 DbContext 中配置查詢過濾器。
創(chuàng)建用于設(shè)置或重置軟刪除屬性的代碼。
∮. 你可以將軟刪除與查詢過濾器的用途(如多租戶使用)結(jié)合使用,但是在查找軟刪除條目時(shí)需要更加小心。
∮. 不要軟刪除一對一的實(shí)體類,因?yàn)樗鼤?dǎo)致問題。
∮. 對于具有關(guān)聯(lián)關(guān)系的實(shí)體類,你需要考慮當(dāng)頂級實(shí)體類被軟刪除時(shí),依賴關(guān)系會發(fā)生什么。
∮. 我介紹了一種實(shí)現(xiàn)級聯(lián)軟刪除的方法,它適用于需要軟刪除其依賴關(guān)系的實(shí)體。
為什么需要軟刪除
當(dāng)你硬刪除(也叫物理刪除)數(shù)據(jù)時(shí),數(shù)據(jù)會從你的數(shù)據(jù)庫中徹底消失。此外,硬刪除還可能硬刪除依賴于所刪除行的行(譯注:默認(rèn)未設(shè)置級聯(lián)刪除規(guī)則的情況下,刪除一行數(shù)據(jù)時(shí),其它通過外鍵關(guān)聯(lián)該行的數(shù)據(jù)都會被級聯(lián)刪除)。就像俗話說的那樣,“當(dāng)它離開了,它就永遠(yuǎn)離開了”——除非你有備份,否則無法取回它。
但現(xiàn)在對數(shù)據(jù)重視度越來越高的環(huán)境下,我們更需要“我使它離開了,但我還可以讓它再回來”。在 Windows 上,它是回收站;如果你在編輯器中刪除了一些文本,你可以用 ctrl-Z 取回它,等等。軟刪除就是是 EF Core 版本的實(shí)體類回收站(實(shí)體類是通過 EF Core 映射到數(shù)據(jù)庫的類的術(shù)語),它從正常使用中消失了,但是你可以取回它。
我的客戶的兩個(gè)應(yīng)用程序廣泛地使用了軟刪除。任何“刪除”的普通用戶確實(shí)設(shè)置了軟刪除標(biāo)志,但一個(gè)管理員用戶可以重置軟刪除標(biāo)志為“取回”用戶。事實(shí)上,我的一個(gè)客戶用“刪除”來表示軟刪除,用“銷毀”來表示硬刪除。保存被軟刪除的數(shù)據(jù)的另一個(gè)好處是歷史記錄——即使是被軟刪除的數(shù)據(jù),你也可以看到過去發(fā)生了什么變化。大多數(shù)客戶的軟刪除數(shù)據(jù)在數(shù)據(jù)庫中保留一段時(shí)間,只在數(shù)月甚至數(shù)年后才把這些數(shù)據(jù)備份或真正刪除。
你可以使用 EF Core 查詢過濾器實(shí)現(xiàn)軟刪除功能。查詢過濾器也用于多租戶系統(tǒng),其中每個(gè)租戶的數(shù)據(jù)只能由屬于同一租戶的用戶訪問。在這種情況下,數(shù)據(jù)被軟刪除,意味著 EF Core 查詢過濾器在隱藏信息時(shí)非常安全的。
我還應(yīng)該說,使用軟刪除也有一些缺點(diǎn)。最主要的缺點(diǎn)是性能。使用軟刪除在每個(gè)實(shí)體類的查詢中包含一個(gè)額外隱藏的 SQL WHERE 子句。
與硬刪除相比,軟刪除處理依賴關(guān)系的方式也有所不同。默認(rèn)情況下,如果您軟刪除一個(gè)實(shí)體類,那么它的依賴關(guān)系不會被軟刪除,而實(shí)體類的硬刪除通常會刪除依賴關(guān)系。這意味著如果我軟刪除了一個(gè) Book 實(shí)體類,那么這本書的評論仍然是可見的,這在某些情況下可能是個(gè)問題。在本文的最后,我將向您展示如何處理這個(gè)問題,并討論一個(gè)可以進(jìn)行級聯(lián)軟刪除的庫。
為你的應(yīng)用添加軟刪除
在本節(jié)中,我將逐一介紹在應(yīng)用程序中添加軟刪除的如下步驟:
向需要軟刪除的實(shí)體類添加軟刪除屬性
向 DbContext 中添加代碼,以對這些實(shí)體類應(yīng)用查詢過濾器
如何設(shè)置/重置軟刪除
在下一節(jié)中,我將詳細(xì)描述這些階段。我假設(shè)一個(gè)典型的 EF Core 類具有普通的讀/寫屬性,但是你可以將它適應(yīng)其他實(shí)體類樣式,比如域驅(qū)動設(shè)計(jì)(DDD)風(fēng)格的實(shí)體類。
1. 添加軟刪除屬性
對于標(biāo)準(zhǔn)的軟刪除實(shí)現(xiàn),你需要一個(gè)布爾標(biāo)志來控制軟刪除。例如,這里有一個(gè)名叫 SoftDeleted 屬性的 Book 實(shí)體。
public?class?Book : ISoftDelete
{
public?int BookId { get; set; }
public?string Title { get; set; }
//... 其它無關(guān)屬性
public?bool SoftDeleted { get; set; }
}
你可以通過它的名字?SoftDeleted?來區(qū)分軟刪除屬性,如果它的值是?true?則該實(shí)體軟刪除了。這意味著當(dāng)你創(chuàng)建一個(gè)新實(shí)體時(shí),它不會被軟刪除。
我還添加了一個(gè) Book 類的 ISoftDelete 接口(第 1 行),這個(gè)接口表示該類必須有一個(gè)可以讀寫的公共 SoftDeleted 屬性。這個(gè)接口將使得在 DbContext 中配置軟刪除查詢過濾器變得更加容易。
2. 配置查詢過濾器
你必須告訴 EF Core 哪個(gè)實(shí)體類需要一個(gè)查詢過濾器,該過濾器是查詢表達(dá)式,用來把不需要被看到的實(shí)體過濾掉。你可以在 DbContext 中使用以下代碼手動完成此操作。
public?class?EfCoreContext : DbContext
{
public?EfCoreContext(DbContextOptionsoption )
: base(options)
{}
protected?override?OnModelCreating(ModelBuilder modelBuilder)
{
// 省略其它和軟刪除無關(guān)的代碼
modelBuilder.Entity().HasQueryFilter(p => !p.SoftDeleted);
}
}
這很好,但是讓我向你展示一種自動添加查詢過濾器的方法。
在 DbContext 中的 OnModelCreating 方法中,你可以通過 Fluent API 配置 EF Core。但是也有一種方法可以判斷每個(gè)實(shí)體類并決定如何配置它。在下面的代碼中,你可以看到 foreach 循環(huán)依次遍歷每個(gè)實(shí)體類,檢查實(shí)體類是否實(shí)現(xiàn)了 ISoftDelete 接口,如果實(shí)現(xiàn)了,它將調(diào)用我創(chuàng)建的擴(kuò)展方法來應(yīng)用正確的軟刪除過濾器配置。
protected?override?void?OnModelCreating(ModelBuilder modelBuilder)
{
// 省略其它無關(guān)的代碼
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
// 省略其它無關(guān)的代碼
if (typeof(ISoftDelete).IsAssignableFrom(entityType.ClrType))
{
entityType.AddSoftDeleteQueryFilter();
}
}
}
有許多配置可以直接應(yīng)用于 GetEntityTypes 方法返回的類型,但是設(shè)置查詢過濾器需要更多的工作。這是因?yàn)椴樵冞^濾器中的 LINQ 查詢需要實(shí)體類的類型來創(chuàng)建正確的 LINQ 表達(dá)式。為此,我創(chuàng)建了一個(gè)小型擴(kuò)展類,它可以動態(tài)創(chuàng)建正確的 LINQ 表達(dá)式來配置查詢過濾器。
public?static?class?SoftDeleteQueryExtension
{
public?static?void?AddSoftDeleteQueryFilter(
this IMutableEntityType entityData)
{
var methodToCall = typeof(SoftDeleteQueryExtension)
.GetMethod(nameof(GetSoftDeleteFilter),
BindingFlags.NonPublic | BindingFlags.Static)
.MakeGenericMethod(entityData.ClrType);
var filter = methodToCall.Invoke(null, new?object[] { });
entityData.SetQueryFilter((LambdaExpression)filter);
}
private?static LambdaExpression GetSoftDeleteFilter()
where TEntity : class, ISoftDelete
{
Expressionbool>> filter = x => !x.SoftDeleted;
return filter;
}
}
我真的很喜歡這個(gè)操作,因?yàn)樗梢怨?jié)省我的時(shí)間,也避免我忘記配置每一個(gè)查詢過濾器。
3. 如何設(shè)置/重置軟刪除
將“軟刪除”屬性設(shè)置為 true 很容易,對應(yīng)的場景是用戶選擇一個(gè)條目并單擊(軟)“刪除”,這會返回實(shí)體的主鍵。用代碼實(shí)現(xiàn)如下:
var entity = context.Books.Single(x => x.BookId == id);
entity.SoftDeleted = true;
context.SaveChanges();
重置軟刪除屬性在實(shí)際的業(yè)務(wù)場景中有點(diǎn)復(fù)雜。首先,你很可能想要向用戶顯示一個(gè)已刪除實(shí)體的列表——把它想象成顯示某個(gè)實(shí)體類類型的實(shí)例回收站,例如 Book。要做到這一點(diǎn),需要在你的查詢中使用 IgnoreQueryFilters 方法,這意味著你將得到所有的實(shí)體(包括那些沒有被軟刪除的和被軟刪除的),然后再根據(jù)需要選出那些 SoftDeleted 屬性為 true 的。
var softDelEntities = _context.Books.IgnoreQueryFilters()
.Where(x => x.SoftDeleted).ToList();
相應(yīng)的,當(dāng)你收到一個(gè)重設(shè) SoftDeleted 屬性的請求時(shí)(它通常包含實(shí)體類的主鍵),則要加載此條目時(shí),需要在查詢中使用 IgnoreQueryFilters 方法。
var entity = context.Books.IgnoreQueryFilters()
.Single(x => x.BookId == id);
entity.SoftDeleted = false;
context.SaveChanges();
使用軟刪除注意事項(xiàng)
首先,需要說的是查詢過濾器是非常安全的。我的意思是,如果查詢過濾器返回 false,那么特定的實(shí)體/行將不會包含在查詢(包括 Find 和 Include 等)返回的結(jié)果集中。你可以使用直接 SQL 繞過它,但除此之外,EF Core 會隱藏你軟刪除的數(shù)據(jù)。
但有幾點(diǎn)你需要注意。
小心軟刪除過濾器與其它過濾器的混合使用
查詢過濾器非常適合于軟刪除,但是查詢過濾器更適合于控制對數(shù)據(jù)組的訪問。例如,假設(shè)您想要構(gòu)建一個(gè) Web 應(yīng)用程序來為多個(gè)公司提供服務(wù),比如工資單。在這種情況下,你需要確保 A 公司看不到 B 公司的數(shù)據(jù),反之亦然。這種類型的系統(tǒng)稱為多租戶應(yīng)用程序,而查詢過濾器非常適合此類場景。
可以參考我的另一篇關(guān)于使用查詢過濾器實(shí)現(xiàn)數(shù)據(jù)訪問控制的文章 bit.ly/3hg6Ptg
問題是,每個(gè)實(shí)體類型只允許使用一個(gè)查詢過濾器,因此,如果您想在多租戶系統(tǒng)中使用軟刪除,那么您必須將這兩個(gè)部分結(jié)合起來形成查詢過濾器——下面是查詢過濾器的示例:
modelBuilder.Entity()
.HasQueryFilter(x => !x.SoftDeleted
&& x.TenantId == currentTenantId);
這看上去很好,但是當(dāng)你使用 IgnoreQueryFilters 方法忽略軟刪除標(biāo)記進(jìn)行查詢時(shí),它會忽略整個(gè)查詢過濾器,包括多租戶部分。因此,如果不小心,還會顯示多租戶數(shù)據(jù)!
答案是為自己構(gòu)建一個(gè)特定于應(yīng)用程序的 IgnoreSoftDeleteFilter 方法,如下所示:
public?static IQueryableIgnoreSoftDeleteFilter (
this IQueryablebaseQuery, string currentTenantId)
where TEntity : class, ITenantId
{
return baseQuery.IgnoreQueryFilters()
.Where(x => x.TenantId == currentTenantId)
}
這將忽略所有篩選器,然后把多租戶篩選器添加回去。這將使它更容易更安全地處理顯示/重置被軟刪除的實(shí)體。
不要軟刪除一對一關(guān)系的實(shí)體類
我曾被邀請幫助客戶開發(fā)一個(gè)非常有趣的系統(tǒng),它對每個(gè)實(shí)體類使用軟刪除。我的客戶發(fā)現(xiàn)你真的不應(yīng)該刪除一對一關(guān)系的實(shí)體。他發(fā)現(xiàn)的問題是,如果你軟刪除一個(gè)一對一關(guān)系,并試圖添加一個(gè)替換的一對一實(shí)體,那么它將失敗。這是因?yàn)橐粚σ魂P(guān)系有一個(gè)唯一的外鍵,而且這個(gè)外鍵已經(jīng)被軟刪除實(shí)體設(shè)置好了,所以在數(shù)據(jù)庫級別上,你無法提供另一個(gè)一對一關(guān)系,因?yàn)橐呀?jīng)存在一個(gè)。
一對一關(guān)系很少,所以在您的系統(tǒng)中它可能不是問題。但如果您確實(shí)需要軟刪除一對一關(guān)系中的實(shí)體,那么我建議將其轉(zhuǎn)換為一對多關(guān)系,確保只有一個(gè)實(shí)體關(guān)閉了軟刪除,我將在下一個(gè)問題中介紹。
譯注:對于大多數(shù)一對一場景,當(dāng)軟刪除一個(gè)實(shí)體時(shí),與其一對一關(guān)聯(lián)的實(shí)體應(yīng)當(dāng)也標(biāo)記為軟刪除。
注意多版本數(shù)據(jù)的軟刪除
在一些業(yè)務(wù)案例中,你可以創(chuàng)建一個(gè)實(shí)體,然后軟刪除它,然后創(chuàng)建一個(gè)新版本。例如,假設(shè)您正在為訂單 1234 創(chuàng)建發(fā)票,然后您被告知訂單已經(jīng)停止,因此你將其軟刪除(這樣您可以保留歷史記錄)。然后,其他人(不知道軟刪除版本的人)被告知?jiǎng)?chuàng)建 1234 的發(fā)票?,F(xiàn)在你有兩個(gè)版本的發(fā)票 1234。這就可能會導(dǎo)致業(yè)務(wù)上的有問題的發(fā)票,特別是當(dāng)有人重置軟刪除的數(shù)據(jù)版本時(shí)。
你有以下方式處理這種情況:
將 DateTime 類型的 LastUpdated 屬性添加到你的 Invoice 實(shí)體類中,使用的是最新的條目,而不是軟刪除條目。
每個(gè)新條目都有一個(gè)版本號,因此在我們的示例中,第一個(gè)發(fā)票的版本號可以是 1234-1,依次為 1234-2。那么,就像 LastUpdated 的版本一樣,版本號最高且沒有被軟刪除的發(fā)票才是要使用的。
通過使用唯一過濾索引,確保只有一個(gè)非軟刪除版本。這是通過為所有未被軟刪除的條目創(chuàng)建一個(gè)惟一的索引來實(shí)現(xiàn)的,這意味著如果你試圖重置已被軟刪除的發(fā)票,但那里已經(jīng)存在一個(gè)已被非軟刪除的發(fā)票,那么你將會得到一個(gè)異常。但同時(shí),你可以有很多歷史軟刪除版本。Microsoft SQL Server RDBMS, PostgreSQL RDBMS, SQLite RDBMS 都有這個(gè)特性(PostgreSQL 和 SQLite 稱為部分索引),據(jù)說 MySQL 出有類似的東西。下面的代碼是 SQL Server 創(chuàng)建唯一過濾索引的示例:
CREATE?UNIQUE?INDEX UniqueInvoiceNotSoftDeleted
ON [Invoices] (InvoiceNumber)
WHERE SoftDeleted = 0
關(guān)于處理因索引問題而出現(xiàn)的異常,請參閱我的文章“Entity Framework Core - validating data and capture SQL error”(地址:bit.ly/3jpRA2W),這篇文章展示了如何將 SQL 異常轉(zhuǎn)換為用戶友好的錯(cuò)誤表示。
如何處理與軟刪除關(guān)聯(lián)的實(shí)體
到目前為止,我們一直在關(guān)注軟刪除/重置單個(gè)實(shí)體,但 EF Core 是關(guān)于關(guān)系的。那么,我應(yīng)該如何處理那些鏈接到被軟刪除的實(shí)體類的關(guān)系呢?為了幫助我們,讓我們看看有不同業(yè)務(wù)需求的兩種不同的關(guān)系。
關(guān)系示例 1:書籍/評論 (Book/Reviews)
在我編寫的書“Entity Framework Core in Action”中,我建立了一個(gè)超級簡單的圖書銷售網(wǎng)站,其中包含書,作者,評論等。在這個(gè)應(yīng)用程序中,我可以刪除一本書。事實(shí)證明,一旦我刪除了這本書,就真的沒有其他途徑可以得到評論了。所以,在這種情況下,我不必?fù)?dān)心被軟刪除的書的評論。
在本書的示例中,我添加了一個(gè)后臺任務(wù)來計(jì)算評論的數(shù)量。下面是我編寫的用于統(tǒng)計(jì)評論的代碼:
var numReviews = await context.Set().CountAsync();
當(dāng)然,無論是否軟刪除,這都會得到相同的計(jì)數(shù),這與硬刪除不同(因?yàn)橛矂h除也會刪除書的評論)。稍后我將介紹如何解決這個(gè)問題。
關(guān)系示例 2:公司/報(bào)價(jià) (Company/Quotes)
在這個(gè)關(guān)系示例中,我向許多公司銷售產(chǎn)品,每個(gè)公司都有一組我們發(fā)送給該公司的報(bào)價(jià)。這是與書籍/評論相同的一對多關(guān)系,但是在本例中,我們有一個(gè)公司列表和一個(gè)單獨(dú)的報(bào)價(jià)列表。所以,如果我軟刪除一個(gè)公司,所有與該公司關(guān)聯(lián)的報(bào)價(jià)附也應(yīng)該被軟刪除。
對于剛才描述的兩個(gè)軟刪除關(guān)系示例,我提出了三個(gè)有用的解決方案。
方案 1:什么也不做,因?yàn)檫@無關(guān)緊要
有時(shí)你軟刪除的一些東西并不重要,但它的關(guān)系仍然可用。如果我軟刪除一本書,在我添加后臺任務(wù)來對評論計(jì)數(shù)之前,我的應(yīng)用程序一直是工作良好的。
譯注:這種情況指的是,當(dāng)軟刪除書籍實(shí)體類時(shí),其關(guān)聯(lián)的評論數(shù)據(jù)一般也不會被訪問到,或者即使被訪問到也無關(guān)緊要。
方案 2:使用聚合根方式
在我那本書中的后臺評論計(jì)數(shù)的示例中,我使用了被稱為聚合的領(lǐng)域驅(qū)動設(shè)計(jì)(DDD)方法作為解決方案。它表示你可以將一起工作的實(shí)體分組,在本例中是 Book、Review 和連接到 Author 表的 BookAuthor。在這樣的組中有一個(gè)根實(shí)體,在本例中是 Book。
正如 Eric Evans 定義 DDD 說的那樣,應(yīng)該始終通過根聚合訪問聚合。在 DDD 中這樣說是有很多原因的,但在這種情況下,它也解決了我們的軟刪除問題,因?yàn)槲覀冎煌ㄟ^ Book(書籍) 訪問 Review(評論) 數(shù)據(jù)。所以 Book 被軟刪除時(shí),與它關(guān)聯(lián)的評論計(jì)數(shù)自然就消失了。因此,可以用下面的代碼替換后臺任務(wù)對 Review 計(jì)數(shù):
var numReviews = await context.Books
.SelectMany(x => x.Reviews).CountAsync();
你還可以通過此方式來查詢公司下面的所有報(bào)價(jià)數(shù)據(jù)。但是還有另一個(gè)方案——模仿數(shù)據(jù)庫級聯(lián)刪除的處理方式,我將在下面介紹。
方案 3:模仿數(shù)據(jù)庫級聯(lián)刪除的方式
數(shù)據(jù)庫有一個(gè)稱為級聯(lián)刪除的設(shè)置,EF Core 有兩種刪除行為(譯注:確切地說有 6 種,這里說兩種應(yīng)該是指其中的與當(dāng)前所講內(nèi)容相關(guān)的兩種),Cascade 和 ClientCascade。這些行為導(dǎo)致硬刪除一行也硬刪除任何依賴于該行的數(shù)據(jù)。例如,在我的圖書銷售應(yīng)用程序中,Book 被稱為主體實(shí)體,而 BookAuthor 鏈接表則是依賴實(shí)體,因?yàn)樗鼈円蕾囉?Book 的主鍵。因此,如果你硬刪除一個(gè) Book 實(shí)體,那么所有鏈接到該實(shí)體的 Review 和 BookAuthor 也會被刪除。如果那些依賴實(shí)體有它們自己的依賴實(shí)體,那么它們也會被刪除——會依次按層次刪除所有依賴實(shí)體。
因此,如果我們復(fù)制級聯(lián)刪除的依賴實(shí)體,將 SoftDeleted 屬性設(shè)置為 true,那么它將軟刪除所有依賴實(shí)體。這是可行的,但當(dāng)你想要重置軟刪除時(shí),它會變得有點(diǎn)復(fù)雜,這就要通過下一部分“處理級聯(lián)軟刪除與重置”來細(xì)說了。
處理級聯(lián)軟刪除與重置
我決定編寫一個(gè)能夠提供級聯(lián)軟刪除解決方案的代碼庫。當(dāng)我開始真正開始編寫此庫時(shí),我發(fā)現(xiàn)各種有趣的事情,我必須解決這個(gè)問題:當(dāng)我們重置軟刪除時(shí),我們希望相關(guān)聯(lián)的實(shí)體回到它們原始的軟刪除狀態(tài)。結(jié)果我發(fā)現(xiàn)我有點(diǎn)復(fù)雜,讓我們用一個(gè)示例來探討我發(fā)現(xiàn)的這個(gè)問題。
回到我們的 Company/Quotes 的例子,來看看如果我們從 Company 到 Quotes 依次設(shè)置其 SoftDeleted 的布爾值會發(fā)生什么(提示:它在某些情況下不起作用)。起先假設(shè)我們有一個(gè)名為 XYZ 的公司,它有兩個(gè)報(bào)價(jià) XYZ-1 和 XYZ-2。然后:
| What | Company | Quotes |
|---|---|---|
| Starting | XYZ | XYZ-1、XYZ-2 |
| Soft delete the quote XYZ-1 | XYZ | XYZ-2 |
| Soft delete Company XZ | - none - | - none - |
| Reset the soft delete on the company XYZ | XYZ | XYZ-1 (wrong!) XYZ-2 |
我們需要做的是制造一個(gè)軟刪除級別,這個(gè)級別告訴你這個(gè)軟刪除設(shè)置到了哪些層。使用這個(gè)我們可以確定我們是否應(yīng)該重置軟刪除。這很復(fù)雜,所以我用一個(gè)圖來表示它是如何工作的。淺色矩形表示被軟刪除的實(shí)體,紅色表示發(fā)生了變化。

這樣,你可以處理級聯(lián)軟刪除/重置問題了。在代碼中有很多小規(guī)則,比如,如果一個(gè)實(shí)體的 SoftDeleteLevel 不是 1,就不能對它的重置,因?yàn)橐粋€(gè)更高級別的實(shí)體軟刪除了它。
我認(rèn)為這種級聯(lián)軟刪除方法是有用的,我已經(jīng)創(chuàng)建了一些原型代碼來實(shí)現(xiàn)到這一點(diǎn),但還需要更多的完善才會把它變成一個(gè) NuGet 庫以便可以在任何系統(tǒng)中使用。如果你對此庫感興趣可以訪問 GitHub 地址:
github.com/JonPSmith/EfCore.SoftDeleteServices
注:這個(gè)庫是在 EF Core 5 預(yù)覽版上構(gòu)建的。
總結(jié)
我們已經(jīng)很清楚地看到了 EF Core 軟刪除所能做的(和不能做的)事情。正如我在開始說的,我在我的兩個(gè)客戶的系統(tǒng)上使用了軟刪除,這對我來說很有意義。軟刪除主要的好處是可以恢復(fù)無意刪除的數(shù)據(jù)和保留歷史記錄。其主要缺點(diǎn)是,軟刪除過濾器可能會降低查詢速度,但可以通過在軟刪除屬性上添加索引來改善性能問題。
根據(jù)我的經(jīng)驗(yàn),我知道軟刪除在商業(yè)應(yīng)用程序中非常好用。我也知道也有一些真實(shí)的場景會用到級聯(lián)軟刪除(正如我客戶的一系統(tǒng))。希望有一天我能有時(shí)間去實(shí)現(xiàn)一個(gè)通用的軟刪除庫。但目前這個(gè)庫已經(jīng)有了一個(gè)原型版本:
github.com/JonPSmith/EfCore.SoftDeleteServices
如果你認(rèn)為你會使用一個(gè)既能處理簡單的軟刪除又能處理級聯(lián)軟刪除的庫,那就給此 repo 加個(gè)星吧。
祝,編程愉快!


開箱即用:.NET Core優(yōu)秀的應(yīng)用邏輯分層框架設(shè)計(jì)

臥槽:微軟又推出新的開源網(wǎng)站!
