EFCore 中的工作單元

一个人走在路上發表於2024-03-30

儲存資料

本文內容
方法 1:更改跟蹤和 SaveChanges
方法 2:ExecuteUpdate 和 ExecuteDelete(“批次更新”)
總結
雖然查詢允許從資料庫中讀取資料,但儲存資料意味著向資料庫新增新實體、刪除實體或以某種方式修改現有實體的屬性。 Entity Framework Core (EF Core) 支援將資料儲存到資料庫的兩種基本方法。

方法 1:更改跟蹤和 SaveChanges
在許多情況下,程式需要查詢資料庫中的某些資料,對其執行一些修改,並儲存這些修改;這有時稱為“工作單元”。 例如,假設你有一組部落格,並且你想要更改其中一個部落格的 Url 屬性。 在 EF 中,這通常按如下方式完成:

c#

複製
using (var context = new BloggingContext())
{
var blog = context.Blogs.Single(b => b.Url == "http://example.com");
blog.Url = "http://example.com/blog";
context.SaveChanges();
}
上述程式碼執行以下步驟:

它使用常規 LINQ 查詢從資料庫載入實體(請參閱查詢資料)。 預設情況下跟蹤 EF 的查詢,這意味著 EF 在其內部更改跟蹤器中跟蹤載入的實體。
透過分配 .NET 屬性來照常操作載入的實體例項。 此步驟不涉及 EF。
最後呼叫 DbContext.SaveChanges()。 此時,EF 會自動檢測任何更改,方法是將實體與載入實體時的快照進行比較。 檢測到的任何更改都將儲存到資料庫;使用關聯式資料庫時,這通常涉及傳送 SQL UPDATE 來更新相關行。
請注意,上面描述了現有資料的典型更新操作,但新增和刪除實體時遵循類似的原則。 透過呼叫 DbSet<TEntity>.Add 和 Remove 與 EF 的更改跟蹤器互動,從而跟蹤更改。 然後,當呼叫 SaveChanges() 時,EF 會將所有跟蹤的更改應用於資料庫(例如,在使用關聯式資料庫時透過 SQL INSERT 和 DELETE)。

SaveChanges() 提供以下優勢:

無需編寫程式碼來跟蹤已更改的實體和屬性 - EF 會自動為你執行此操作,並且僅更新資料庫中的這些屬性,從而提高效能。 想象一下,如果載入的實體繫結到 UI 元件,允許使用者更改他們想要的任何屬性;EF 減輕了找出哪些實體和屬性實際已更改的負擔。
儲存對資料庫的更改有時可能很複雜! 例如,如果要新增一個部落格併為該部落格新增一些帖子,則可能需要為插入的部落格提取資料庫生成的金鑰,然後才能插入帖子(因為它們需要引用部落格)。 EF 為你完成所有這些操作,從而消除了複雜性。
EF 可以檢測併發問題,例如,當其他人在你的查詢和 SaveChanges() 之間修改了資料庫行時。 併發衝突中提供了更多詳細資訊。
在支援它的資料庫中,SaveChanges() 自動包裝事務中的多個更改,確保在發生故障時資料保持一致。 事務中提供了更多詳細資訊。
在許多情況下,SaveChanges() 還會對多個更改進行批處理,從而顯著減少資料庫往返次數並大幅提高效能。 高效更新中提供了更多詳細資訊。
有關基本 SaveChanges() 用法的詳細資訊和程式碼示例,請參閱基本 SaveChanges。 有關 EF 更改跟蹤的詳細資訊,請參閱更改跟蹤概述。

方法 2:ExecuteUpdate 和 ExecuteDelete(“批次更新”)
備註

EF Core 7.0 中已引入此功能。

雖然更改跟蹤和 SaveChanges() 是儲存更改的強大方法,但它們確實存在某些缺點。

首先,SaveChanges() 要求查詢和跟蹤要修改或刪除的所有實體。 如果需要刪除評分低於特定閾值的所有部落格,則必須查詢、具體化和跟蹤可能大量的行,並讓 SaveChanges() 為每個行生成 DELETE 語句。 關聯式資料庫提供了一個更高效的替代方法:可以傳送單個 DELETE 命令,透過 WHERE 子句指定要刪除的行,但 SaveChanges() 模型不允許生成該行。

若要支援此“批次更新”方案,可以使用 ExecuteDelete,如下所示:

c#

複製
context.Blogs.Where(b => b.Rating < 3).ExecuteDelete();
這允許透過常規 LINQ 運算子(類似於常規 LINQ 查詢)來表達 SQL DELETE 語句,從而針對資料庫執行以下 SQL:

SQL

複製
DELETE FROM [b]
FROM [Blogs] AS [b]
WHERE [b].[Rating] < 3
這會在資料庫中非常高效地執行,無需從資料庫載入任何資料或涉及 EF 的更改跟蹤器。 同樣,ExecuteUpdate 允許表達 SQL UPDATE 語句。

即使未批次更改實體,也可能確切地知道要更改哪個實體的哪些屬性。 使用更改跟蹤 API 執行更改可能過於複雜,需要建立實體例項,透過 Attach 跟蹤它,進行更改,最後呼叫 SaveChanges()。 對於此類方案,ExecuteUpdate 和 ExecuteDelete 可能是表示相同操作的更簡單方法。

最後,更改跟蹤和 SaveChanges() 本身都會產生特定的執行時開銷。 如果要編寫高效能應用程式,則使用 ExecuteUpdate 和 ExecuteDelete 可以避免這兩個元件並有效地生成所需的語句。

但是,請注意,ExecuteUpdate 和 ExecuteDelete 也有一些限制:

這些方法會立即執行,目前無法與其他操作一起批處理。 另一方面,SaveChanges() 可以同時批處理多個操作。
由於不涉及更改跟蹤,因此你有責任確切地知道需要更改哪些實體和屬性。 這可能意味著更多的手動、低階別程式碼跟蹤需要更改的內容和不需要更改的內容。
此外,由於不涉及更改跟蹤,因此在保留更改時,這些方法不會自動應用併發控制。 但是,你仍然可以顯式新增 Where 子句來自行實現併發控制。
目前僅支援更新和刪除;必須透過 DbSet<TEntity>.Add 和 SaveChanges() 完成插入。
有關詳細資訊和程式碼示例,請參閱 ExecuteUpdate 和 ExecuteDelete。

總結
下面是有關何時使用哪種方法的一些準則。 請注意,這些不是絕對規則,但提供了有用規則的縮圖:

如果事先不知道將發生哪些更改,請使用 SaveChanges;它將自動檢測需要應用的更改。 示例方案:
“我想要從資料庫載入部落格並顯示允許使用者更改它的表單”
如果需要操作一個物件圖(即多個相互連線的物件),請使用 SaveChanges;它將確定更改的正確順序以及如何將所有內容連結在一起。
“我想要更新部落格,更改其中某些帖子並刪除其他帖子”
如果要根據某些條件更改可能大量的實體,請使用 ExecuteUpdate 和 ExecuteDelete。 示例方案:
“我想要給所有員工加薪”
“我想要刪除名稱以 X 開頭的所有部落格”
如果已經確切地知道要修改哪些實體以及如何更改它們,請使用 ExecuteUpdate 和 ExecuteDelete。 示例方案:
“我想要刪除名稱為‘Foo’的部落格”
“我想要將 ID 為 5 的部落格名稱更改為 'Bar'”

相關文章