推薦關注「碼俠江湖」加星標,時刻不忘江湖事
這是 EF Core 系列的第六篇文章,上一篇文章講述了 EF Core 中的原始 SQL 語句查詢。
這篇文章講一講 EF Core 如何修改實體數據。
點擊上方或后方藍字,閱讀 EF Core 系列合集。
實體狀態
在開始學習 EF Core 修改數據之前,我們必須要熟悉 EF Core 中的一些機制。
前面的內容中,我們講到 DbContext
包括三個屬性:ChangeTracker
、Database
和 Model
。
之前的示例中,Database
和 Model
我們都有用到過,那么 ChangeTracker
是做什么的?
ChangeTracker
屬性提供了對當前加載的實體的,變化跟蹤信息和操作的訪問。
當我們想執行任何數據庫修改操作時,無論是創建、修改還是刪除一個實體,EF Core 都有關于跟蹤和操作的信息。
為什么要保存這些信息呢?
因為在 EF Core 中,修改實體屬性的值,并不會直接被保存到數據庫中。
而是要我們調用 SaveChanges
方法,EF Core 才會將修改反應到數據庫,在此之前,EF Core 不會執行任何操作。
因此,在調用 SaveChanges
方法之前, EF Core 需要知道我們都執行了什么操作,這對 EF Core 來說非常重要。
每一個被追蹤的實體,都有附屬于它的 State
屬性。
當我們使用上下文對象加載實體,而不使用 AsNoTracking
方法時,或者我們通過 Update
、Remove
或 Add
方法,改變實體狀態時,該實體都會成為被跟蹤的實體。
狀態屬性的值可以通實體的 State
方法獲得。
那么實體的狀態有哪些呢?
「Detached」 - 該實體沒有被追蹤,調用 SaveChanges 方法不會有任何效果
「Unchanged」 - 該實體從數據庫中加載,但沒有任何變化。調用 SaveChanges 方法也不會有任何效果
「Added」 - 該實體不存在于數據庫中,調用 SaveChanges 方法會將其添加到數據庫中。
「Modified」 - 該實體存在于數據庫中,并被修改過,因此,調用 SaveChanges 方法將在數據庫中修改它
「Deleted」 - 該實體存在于數據庫中,調用 SaveChanges 方法,會將它從數據庫中刪除。
實體操作
接下來,讓我們結合示例來演示增刪改,并觀察實體狀態的變化:
var?account?=?new?Account
{Name?=?"張三",Age?=?18
};
_context.Add(account);
_context.SaveChanges();
示例中,首先創建了一個 account
對象,此時它還未被附加到 EF Core 上下文中,它的狀態應該是 Detached
。
當我們使用 Add
方法,將account
對象作為實體添加到上下文后,它的狀態應該是 Added
。
緊接著,使用 SaveChanges
方法,將新添加的實體,保存到數據庫中,它的狀態應該是 Unchanged
。
當我們再次修改實體中的任意屬性時,它的狀態會變成 Modified
。
//?...
account.Age?=?20;
_context.SaveChanges();
使用 SaveChanges
方法,將已修改的實體,應用到數據庫時,它的狀態又會變回 Unchanged
。
最后,當我們從上下文中刪除這個實體后,實體的狀態會變成 Deleted
。
//?...
_context.Remove(account);
_context.SaveChanges();
再次使用 SaveChanges
方法,將實體從數據庫中徹底刪除,大家想一想,此時它的狀態依然會變成什么?
這次可不是 Unchanged
了,因為它已經從上下文中被移除了,所以它的狀態是 Detached
。
EF Core 正是用這么幾個方法,對實體進行增刪改。
當然,這只是其中一種方式。
在這個示例中,新增的對象經過 Add
方法,被添加到了上下文。
之后的更新和刪除操作,都是針對實體已經存在于上下文中的情況。
那么如果,我們有一個對象,它不存在于上下文中,但它確實存在于數據庫中,我們該如何對它就行更新或刪除呢?
首先,你肯定不能用 Add
方法,雖然它可以將一個對象,作為實體添加到上下文中
但它還會使實體狀態成為 Added
,而不是 Modified
,這種情況下執行 SaveChanges
方法,EF Core 只會生成插入數據的語句,肯定會造成數據庫沖突,因為數據已存在。
比如,這個對象:
var?account?=?new?Account
{Id?=?new?Guid("dd4feb0b-b57c-4338-a40a-7aa73fc6e460"),Name?=?"Zilor",Age?=?99
};
實際開發中,它可能來自的客戶端,這個對象的數據是經過之前的查詢得到的。
現在客戶端對它的屬性進行了修改,主鍵肯定是不會變的。
此時,它的狀態是 Detached
,沒有被附加到上下文中,我們該如何直接更新到數據庫呢?
這里有兩種方法可以做到:
一是先用 ID
從 EF Core 中查詢出實體,然后用 account
對象中的屬性值,修改實體中的值,再使用 SaveChanges
保存數據。
比如這樣:
var?dbAccount?=?_context.Accounts.FirstOrDefault(a?=>?a.Id?==?account.Id);
dbAccount.Name?=?account.Name;
dbAccount.Age?=?account.Age;
_context.SaveChanges();
需要注意的是,如果修改的操作,沒有造成實體值的任何變化,實體狀態將仍是 Unchanged
。
此時,即便執行SaveChanges
方法, 也不會有任何效果。
二是用附加方法,將 account
對象附加到上下文,然后手動修改它的狀態,再使用 SaveChanges
保存數據。
比如這樣:
var?account?=?new?Account
{Id?=?new?Guid("dd4feb0b-b57c-4338-a40a-7aa73fc6e460"),Name?=?"Zilor",Age?=?99
};_context.Accounts.Attach(account);
_context.Entry(account).State?=?EntityState.Modified;
_context.SaveChanges();
Attach
方法用來附加實體,被附加的實體初始狀態為 Unchanged
,所以我們要手動修改實體狀態。
執行應用,可以看到實體狀態的流轉。
第二種方式相對第一種方式,少了一步查詢,也少了實體屬性的賦值。
但是,無論實體的值相對于數據庫是否有變化,更新操作都會執行。
這是方式適合全量更新,因為如果客戶端傳來的是不完整的對象,只包含了修改的屬性,那么更新時可能會造成問題。
比如這個例子,如果 Account
對象中沒有 Age
屬性,更新到數據庫時,Age
列的值會是個 「Null」。
第一種方式,由于是從實體查詢出來的數據,數據是完整的,修改也只是針對個別屬性,所以保存數據時,不會發生這種情況。
而且第一種方式,可以判斷出實體是否真的已經修改,無修改的話,不會執行任何操作。
所以,對于更新操作,建議使用第一種方式,更安全也更靠譜;
刪除操作也是如此,只需要將實體狀態修改為 Deleted
,就可以直接刪除,不夠刪除操作可以使用第二種方式。
更多精彩內容,請關注我▼▼
如果喜歡我的文章,那么
在看和轉發是對我最大的支持!
(戳下面藍字閱讀)
ASP.NET 6 中間件系列
查缺補漏系統學習 EF Core 6? 系列
推薦關注微信公眾號:碼俠江湖
? ? ? ? ? ? ? ? ? ? ? ??覺得不錯,點個在看再走喲