EF Core – Unit of Work, DbContext, Transaction 概念解釋

前言

踩了一個坑, 下面是 2 個 scope 的調用, 第 1 和 3 是一個 Audit log filter action, 第 2 個是 controller.

// open tran
// edit entity 1
// save change 1

// save point A
// edit entity 2
// save change 2 (fail, let's said due to sql unique constrain)
// catch { rollback to save point A }

// edit entity 3
// save change 3 

// commit (expected 1,3 will be saved)

需求是即使 controller 報錯了, 但是 audit log 還是要記入. 直接認爲 controller 報錯或 rollback to save point 就 ok 了, 

但最終結果是 save change 3 也報錯了. 因爲 rollback to save point 並不會 rollback dbcontext state. 與至於 save change 3 耶跑了 edit entity 2, 這就報錯了.

然後我就找到了這個 issue: DbContextTransaction should also rollback dbcontext, 提問者視乎也遇到了相同的情況. 

 

參考:

Use both AddDbContextFactory() and AddDbContext() extension methods in the same project

Scoped service in DBContext with DBContextFactory can't resolve from root provider

DbContextTransaction should also rollback dbcontext

DbContext Lifetime, Configuration, and Initialization

 

DbContext = Unit of Work

在 EF Core Team 的回覆中, 他們的角度是. 如果想要有一個 unit of work, 就開 1 個 dbcontext. 

在這個 dbcontext 裏面, 它負責維護修修改改的 entity state, 當 save changes 調用時, 它把所有 state 轉換成 SQL query.

如果 save changes 順利執行, 那麼所有的 state 會 clear 掉 (變成 unchanged), 然後你可以開始下一輪的 unit of work.

如果 save changes 失敗, 那麼所有的 state 會保留着, 你可以修修改改在重試. 

絕大部分情況下, 1 個 http request 用一個 unit of work 就足夠了, 也就是一個 dbcontext 來 handle 就很恰當了, 所以默認 dbcontext 在 DI 是 scoped level.

但某些情況下可能會想要多個 unit of work for 1 http request, 這也是合理的.

可以使用 DbContextFactory 來創建.

要注意 lifetime, 它默認是 Singleton, 如果 ApplicationDbContext 有注入 scoped service 的話, 這裏要改成 scoped.

總結, dbcontext 就像一個環境, 管理 entity state, 在一個 http request 中, 可以創建多個相同的 dbcontext, 來執行多個 unit of work, 這取決於想怎樣去管理.

 

Unit of Work != Transaction

dbcontext 可以開啓 transaction, 但也可以複用 transaction. 意味着, 多個 unit of work 是可以共享 transaction 的.

dbcontext save changes 以後, unit of work 就算完成了. 後續它有沒有真的寫入數據庫, 這個得開 transaction 是否 commit/rollback.

所以可以理解 dbcontext 和 transaction 的職責是分的很開的. 這也是爲什麼 transaction rollback, dbcontext 卻沒有 rollback, 因爲它們本來就沒有什麼牽連.

dbcontext save changes 成功的話, state 立馬就 clear 了. 但這時 transaction 要 commit/rollback 根本還沒有決定. 它纔開始進入這個 step 而已.

 

總結

dbcontext = unit of work

1 個 http request 可以有多個 dbcontext 管理 entity state (比如 1 個 for audit log, 1 個 for controller, 它們互相不影響)

在 save changes 失敗或, entity state 依舊會保留着, 可以 retry 也可以通過 dbContext.ChangeTracker.Clear() 清空它, 它可以把 Added 變成 Detached.

1 transaction 可以 shared with multiple dbcontext.

transaction 接手 dbcontext save changes 後的工作. 它決定那麼 save changes 是否要永久寫入數據庫. 或者回滾, 或者回滾某些 save changes (基於 save point)

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章