我有以下方法更新物體。我唯一的分歧是,當提供了一個不存在的 ID 時,我得到了一個嚴厲的例外。
public bool Update(Thing thing)
{
Context.Things.Update(thing);
int result = Context.SaveChanges();
return result == 1;
}
所以我添加了一個檢查來控制拋出的例外(加上一些不錯的日志記錄和其他便利)。最終,我計劃完全跳過嘔吐。
public bool UpdateWithCheck(Thing thing)
{
Thing target = Context.Things.SingleOrDefault(a => a.Id == thing.Id);
if (target == null)
throw new CustomException($"No thing with ID {thing.Id}.");
Context.Things.Update(thing);
int result = Context.SaveChanges();
return result == 1;
}
不,這不起作用,因為物體已經被跟蹤。我有幾個選擇來處理這個問題。
- 更改為
Context.Where(...).AsNoTracking()。 - 在 target 中顯式設定更新的欄位并保存。
- 使用物體狀態并篡改跟蹤器。
- 洗掉當前并添加新的。
我無法決定哪個是最佳實踐。谷歌搜索給我的默認示例不包含在同一操作中檢查預先存在的狀態。
uj5u.com熱心網友回復:
出現例外的原因是因為通過從 Context 加載物體來檢查它是否存在,您現在有了一個跟蹤參考。當你去更新分離的參考時,EF 會抱怨一個實體已經被跟蹤。
最簡單的解決方法是:
public bool UpdateWithCheck(Thing thing)
{
bool doesExist = Context.Things.Any(a => a.Id == thing.Id);
if (!doesExist)
throw new CustomException($"No thing with ID {thing.Id}.");
Context.Things.Update(thing);
int result = Context.SaveChanges();
return result == 1;
}
但是,這種方法有兩個問題。首先,因為我們不知道 DbContext 實體的范圍或不能保證方法的順序,所以在某個時候 DbContext 實體可能已經加載并跟蹤了事物的那個實體。這可能表現為看似間歇性的錯誤。防止這種情況的正確方法是:
public bool UpdateWithCheck(Thing thing)
{
bool doesExist = Context.Things.Any(a => a.Id == thing.Id);
if (!doesExist)
throw new CustomException($"No thing with ID {thing.Id}.");
Thing existing = Context.Things.Local.SingleOrDefault(a => a.Id == thing.Id);
if (existing != null)
Context.Entry(existing).State = EntityState.Detached;
Context.Things.Update(thing);
int result = Context.SaveChanges();
return result == 1;
}
這會檢查任何已加載實體的本地跟蹤快取,如果找到,則將它們分離。這里的風險是,任何沒有保留在那些被跟蹤的引??用中的修改都將被丟棄,并且任何浮動的、本應附加的參考現在都將被分離。
第二個重要問題是使用Update(). 當您將分離的物體傳遞出去時,您不打算更新的資料可能會被更新。更新將替換所有列,通常如果客戶端可能只需要更新其中的一個子集。EF 可以配置為在更新之前根據資料庫檢查物體上的行版本或時間戳,當您的資料庫設定為支持它們時(例如快照隔離),這可以幫助防止過時覆寫,但仍然允許意外篡改。
正如您已經發現的那樣,更好的方法是避免傳遞分離的物體,而是使用專用的 DTO。這避免了關于哪些物件代表視圖/消費者狀態與資料狀態的潛在混淆。通過將值從 DTO 顯式復制到物體,或配置映射器以復制受支持的值,您還可以保護您的系統免受意外篡改和潛在的陳舊覆寫。這種方法的一個考慮因素是,您應該通過確保您的物體和 DTO 具有要比較的 RowVersion/Timestamp 來保護更新,以避免無條件地用潛在的陳舊資料覆寫資料。在從 DTO 復制到新加載的物體之前,比較版本,如果匹配,則自您獲取和組合 DTO 以來,資料行中沒有任何更改。如果變了,這意味著自從讀取 DTO 以來,其他人已經更新了基礎資料行,因此您的修改是針對陳舊資料的。從那里,采取適當的行動,例如放棄更改、覆寫更改、合并更改、記錄事實等。
uj5u.com熱心網友回復:
只需更改target和呼叫的屬性SaveChanges()- 洗掉更新呼叫。我想說的是,如今的典型用例是輸入thing實際上不是 aThing而是 a ThingViewModel,ThingDto或“一個攜帶足夠資料以識別和更新事物但實際上不是一個資料庫物體”。在這種情況下,如果手動從 ThingViewModel 更新 Thing 屬性的概念讓您感到厭煩,您可以查看一個映射器(AutoMapper 可能是最著名的,但還有很多其他的)來為您進行復制,甚至設定您如果您決定將此方法轉換為 Upsert,則可以使用新方法
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/359176.html
上一篇:如何在EFCore5中從一對一和一對多關系中獲取資料
下一篇:在物體框架中更新物體時執行操作
