ExecuteUpdate 和 ExecuteDelete
注意
此功能是在 EF Core 7.0 中引入。
ExecuteUpdate 和 ExecuteDelete 是將數據儲存至資料庫的方法,而不需使用EF的傳統變更追蹤和 SaveChanges() 方法。 如需這兩種技術的簡介比較,請參閱 儲存數據的概觀頁面 。
ExecuteDelete
假設您需要刪除評等低於特定閾值的所有部落格。 傳統 SaveChanges() 方法會要求您執行下列動作:
foreach (var blog in context.Blogs.Where(b => b.Rating < 3))
{
context.Blogs.Remove(blog);
}
context.SaveChanges();
這是執行這項工作相當沒有效率的方式:我們會查詢資料庫以取得符合篩選條件的所有部落格,然後查詢、具體化和追蹤所有這些實例:相符實體的數目可能很大。 接著,我們會告訴 EF 的變更追蹤器,每個部落格都需要移除,並藉由呼叫 SaveChanges()來套用這些變更,這會為每個和每個部落格產生 DELETE
語句。
以下是透過 ExecuteDelete API 執行的相同工作:
context.Blogs.Where(b => b.Rating < 3).ExecuteDelete();
這會使用熟悉的 LINQ 運算符來判斷哪些部落格應該受到影響,就像我們正在查詢一樣,然後告訴 EF 對資料庫執行 SQL DELETE
:
DELETE FROM [b]
FROM [Blogs] AS [b]
WHERE [b].[Rating] < 3
除了更簡單且較短之外,這在資料庫中執行得非常有效率,而不需要從資料庫載入任何數據,或涉及EF的變更追蹤器。 請注意,您可以使用任意 LINQ 運算符來選取您想要刪除的部落格 -- 這些部落格會轉譯為 SQL 以在資料庫中執行,就像您查詢這些部落格一樣。
ExecuteUpdate
與其刪除這些部落格,如果我們想要變更屬性以指出應該改為隱藏它們,該怎麼辦? ExecuteUpdate 提供類似的方式來表示 SQL UPDATE
語句:
context.Blogs
.Where(b => b.Rating < 3)
.ExecuteUpdate(setters => setters.SetProperty(b => b.IsVisible, false));
和 一 ExecuteDelete
樣,我們會先使用 LINQ 來判斷哪些部落格應該受到影響;但 ExecuteUpdate
我們也需要表達要套用至相符部落格的變更。 這是藉由在呼叫內呼叫SetProperty
來完成,並提供兩個自變數:要變更的屬性(IsVisible
),以及它應該有的新值(false
)。ExecuteUpdate
這會導致執行下列 SQL:
UPDATE [b]
SET [b].[IsVisible] = CAST(0 AS bit)
FROM [Blogs] AS [b]
WHERE [b].[Rating] < 3
更新多個屬性
ExecuteUpdate
允許在單一調用中更新多個屬性。 例如,若要同時將 設定 IsVisible
為 false 和 設為 Rating
零,只需將其他 SetProperty
呼叫鏈結在一起:
context.Blogs
.Where(b => b.Rating < 3)
.ExecuteUpdate(setters => setters
.SetProperty(b => b.IsVisible, false)
.SetProperty(b => b.Rating, 0));
這會執行下列 SQL:
UPDATE [b]
SET [b].[Rating] = 0,
[b].[IsVisible] = CAST(0 AS bit)
FROM [Blogs] AS [b]
WHERE [b].[Rating] < 3
參考現有的屬性值
上述範例會將 屬性更新為新的常數值。 ExecuteUpdate
也允許在計算新值時參考現有的屬性值;例如,若要將所有相符部落格的評等增加一個,請使用下列各項:
context.Blogs
.Where(b => b.Rating < 3)
.ExecuteUpdate(setters => setters.SetProperty(b => b.Rating, b => b.Rating + 1));
請注意,的第 SetProperty
二個自變數現在是 Lambda 函式,而不是先前的常數。 其 b
參數代表正在更新的 Blog;在該 Lambda 內, b.Rating
因此會在發生任何變更之前包含評等。 這會執行下列 SQL:
UPDATE [b]
SET [b].[Rating] = [b].[Rating] + 1
FROM [Blogs] AS [b]
WHERE [b].[Rating] < 3
導覽和相關實體
ExecuteUpdate
目前不支援在 Lambda 內 SetProperty
參考導覽。 例如,假設我們想要更新所有部落格的評等,讓每個部落格的新評等都是其所有文章評等的平均值。 我們可能會嘗試使用 ExecuteUpdate
,如下所示:
context.Blogs.ExecuteUpdate(
setters => setters.SetProperty(b => b.Rating, b => b.Posts.Average(p => p.Rating)));
不過,EF 確實允許先使用 Select
來計算平均評等,並將其投影至匿名類型,然後再使用該 ExecuteUpdate
作業:
context.Blogs
.Select(b => new { Blog = b, NewRating = b.Posts.Average(p => p.Rating) })
.ExecuteUpdate(setters => setters.SetProperty(b => b.Blog.Rating, b => b.NewRating));
這會執行下列 SQL:
UPDATE [b]
SET [b].[Rating] = CAST((
SELECT AVG(CAST([p].[Rating] AS float))
FROM [Post] AS [p]
WHERE [b].[Id] = [p].[BlogId]) AS int)
FROM [Blogs] AS [b]
變更追蹤
熟悉 SaveChanges
的使用者可用來執行多個變更,然後呼叫 SaveChanges
以將所有這些變更套用至資料庫;EF 的變更追蹤器可以進行這項變更,這些變更會累積或追蹤這些變更。
ExecuteUpdate
和 ExecuteDelete
的運作方式大不相同:它們會在叫用時立即生效。 這表示,雖然單 ExecuteUpdate
一或 ExecuteDelete
作業可能會影響許多數據列,但無法累積多個這類作業並一次套用它們,例如呼叫 SaveChanges
時。 事實上,函式完全不知道 EF 的變更追蹤器,而且沒有任何與其互動。 這有幾個重要的後果。
請考慮下列程式碼:
// 1. Query the blog with the name `SomeBlog`. Since EF queries are tracking by default, the Blog is now tracked by EF's change tracker.
var blog = context.Blogs.Single(b => b.Name == "SomeBlog");
// 2. Increase the rating of all blogs in the database by one. This executes immediately.
context.Blogs.ExecuteUpdate(setters => setters.SetProperty(b => b.Rating, b => b.Rating + 1));
// 3. Increase the rating of `SomeBlog` by two. This modifies the .NET `Rating` property and is not yet persisted to the database.
blog.Rating += 2;
// 4. Persist tracked changes to the database.
context.SaveChanges();
關鍵是,叫用 時 ExecuteUpdate
且資料庫中所有部落格都會更新,EF 的變更追蹤器 不會 更新,且追蹤的 .NET 實例仍具有其原始評等值,從查詢的時間點開始。 假設部落格的評等原本為5;在第三行執行之後,資料庫中的評等現在是 6(因為 ,因為 ), ExecuteUpdate
而追蹤的 .NET 實例中的評等為 7。 呼叫 時 SaveChanges
,EF 會偵測到新的值 7 與原始值 5 不同,並保存該變更。 所 ExecuteUpdate
執行的變更會遭到覆寫,而不會納入考慮。
因此,通常最好避免透過 混合追蹤 SaveChanges
的修改和未追蹤的 ExecuteUpdate
/ExecuteDelete
修改。
交易
繼續上述內容,請務必瞭解 ExecuteUpdate
,且 ExecuteDelete
不會隱含地啟動其叫用的交易。 請考慮下列程式碼:
context.Blogs.ExecuteUpdate(/* some update */);
context.Blogs.ExecuteUpdate(/* another update */);
var blog = context.Blogs.Single(b => b.Name == "SomeBlog");
blog.Rating += 2;
context.SaveChanges();
每個 ExecuteUpdate
呼叫都會將單一 SQL UPDATE
傳送至資料庫。 由於不會建立任何交易,如果任何失敗導致第二 ExecuteUpdate
個失敗無法順利完成,則第一個交易的效果仍會保存至資料庫。 事實上,上述四個作業 - 兩個 的調用 ExecuteUpdate
,一個查詢和 SaveChanges
- 各自在自己的交易內執行。 若要在單一交易中包裝多個作業,請使用 明確啟動交易 DatabaseFacade:
using (var transaction = context.Database.BeginTransaction())
{
context.Blogs.ExecuteUpdate(/* some update */);
context.Blogs.ExecuteUpdate(/* another update */);
...
}
如需交易處理的詳細資訊,請參閱 使用交易。
並行控制和受影響的數據列
SaveChanges
提供自動 並行控制,使用並行令牌來確保載入數據列與儲存變更的時間之間不會變更。 由於 ExecuteUpdate
和 ExecuteDelete
不會與變更追蹤器互動,因此無法自動套用並行控制。
不過,這兩種方法都會傳回受作業影響的數據列數目;這特別方便您自行實作並行控制:
// (load the ID and concurrency token for a Blog in the database)
var numUpdated = context.Blogs
.Where(b => b.Id == id && b.ConcurrencyToken == concurrencyToken)
.ExecuteUpdate(/* ... */);
if (numUpdated == 0)
{
throw new Exception("Update failed!");
}
在此程式代碼中,我們會使用 LINQ Where
運算子將更新套用至特定部落格,而且只有在其並行令牌具有特定值時,才會套用更新(例如,從資料庫查詢部落格時看到的值)。 接著,我們會檢查實際更新 ExecuteUpdate
的數據列數目;如果結果為零,則不會更新任何數據列,而且並行令牌可能會因為並行更新而變更。
限制
- 目前僅支援更新和刪除;插入必須透過 DbSet<TEntity>.Add 和 SaveChanges()來完成。
- 雖然 SQL UPDATE 和 DELETE 語句允許擷取受影響數據列的原始數據行值,但 和
ExecuteDelete
目前ExecuteUpdate
不支援。 - 無法批處理這些方法的多個調用。 每個調用都會對資料庫執行自己的往返。
- 資料庫通常只允許使用 UPDATE 或 DELETE 修改單一數據表。
- 這些方法目前僅適用於關係資料庫提供者。