EF和EF Core 的DbContext類實現IDisposable介面。因此,很多最佳程式設計實踐中都建議你將它們放在一個using()塊中。不幸的是,至少在Web應用程式中,這樣做通常不是一個好主意。
我與許多從.NET Framework遷移到.NET Core和.NET 5的客戶一起工作,其中一些客戶在舊版應用程式中並沒有使用依賴項注入,或者沒有一直使用它。結果導致他們的DbContext類有大量的例項。這樣做有很多問題,其中最重要的是它導致了緊耦合。
“在Web應用程式中,每個Web請求應該有且只有一個DbContext。”
如果您遵循上述規則,則一切正常。否則可能會有很多麻煩。比如你會遇到這樣的問題:如何跟蹤實體,或被跟蹤的實體在你認為應儲存時並沒有儲存(稍後對此進行詳細介紹)。尤其是如果使用非同步程式碼,你可能會發現DbContext被釋放的意外情況,這可能需要一些時間來解決。
“在ASP.NET或ASP.NET Core中,配置DbContext最理想的方法是通過DI容器。”
如果讓DI容器(如Autofac)幫你管理DbContext例項及其生命週期,就可以避免以上所有這些麻煩。如果您還使用倉儲或類似的抽象,請確保它們的生命週期與DbContext的生命週期一致。ASP.NET Core內建的DI容器和Helper能在Scoped 生命週期型別中為EF Core做出正確的配置。這意味著每個請求將建立一個新的DbContext例項。且這個請求中,任何想使用DbContext例項的操作都共享同一例項。在請求結束時將其清理並釋放。如果您使用的是Autofac和EF 6,Scoped就是每個請求只使用一個例項的意思。
using語句和DbContext
使用using語句可能的問題:
- 您釋放了一個DbContext,使得一個實體無法儲存
- 您非同步地把DbContext傳遞給另一個服務,但是在它被(另一個服務)使用前,就在原始服務中的using塊中將其釋放
- 當同時當使用建構函式注入並在ConfigureServices 或者 一個Autofac 模組中新增一行程式碼這種正確的的行為時(即同時使用using和容器管理),整個應用程式範圍內顯得程式碼(不必要的)重複和混亂。
多個DbContext的問題
與using語句和DbContext密切相關的問題是:如果有多個DbContext(因為該using語句通常會建立一個新例項)。
“如果您有多個DbContext嘗試使用相同的實體例項,那麼您將承受巨大的痛苦。”
考慮這個簡單的示例,它包含了一個Controller和一個service,兩者同時在使用dbContext。
假設在Index方法中,從資料庫中讀取 starship時,它的name屬性值為“ Millenium Falcon”。下次訪問Index時,name屬性值是什麼?
SaveChanges()被呼叫時不執行任何操作。db例項在StarshipService中並未跟蹤該實體。所以名稱將保持不變。
那麼,我們可以這樣解決這個問題。讓我們附加實體。這是更新後的service:
再次執行。現在Name的值是什麼?
SaveChanges()仍然不執行任何操作。實體的name被更新,該實體依然未跟蹤。
讓我們再嘗試一次:
現在,它會起作用嗎?你覺得如何?
是的,現在name的值已更新為“ Millenium Falcon *”,下次是“ Millenium Falcon **”,等等。
要正確地得到預期的行為,需要進行大量的嘗試工作。這段程式碼是脆弱且重複的,更糟的是,具有隱藏的暫存依賴性。
那麼,MVC5/EF6的舊程式碼中要怎麼使用Autofac呢?
安裝Nuget軟體包
安裝Autofac.MVC5 nuget軟體包。與.NET Framework nuget包不同,它不會新增一堆類,但你仍然需要將它們連線起來。
進到global.asax檔案中更新Application_Start():
當你成功執行它,你還可以把它們放置到一個模組中,並把它的helper放到在App_Start目錄中。
這部分程式碼中所做的事情有:
- 配置Autofac的依賴項容器
- 設定它,以建立controller
- 設定它,以建立 ApplicationDbContext
- 設定它,以建立 StarshipService
- 使用 InstancePerRequest設定上面兩個
- 配置MVC5以使用Autofac來解決其依賴性
這可能也就需要用5分鐘。更新之後還會有混亂的程式碼嗎?好了,服務現在看起來像這樣:
另外,控制器現在看起來像這樣:
請注意,這兩種型別現在都遵循顯式依賴項原則。同時注意到,在解耦之後的程式碼沒完全沒有new關鍵字,這與顯式依賴原則有關。
“方法和類應顯式要求(通常通過方法引數或建構函式引數)它們所需要的協作物件才能正常執行。”
不用對所引用物件的隱式依賴感到驚訝。如果需要引用某個類,應該在建構函式中引入它(注:依賴注入)。如果要將類進行解耦,,則應該宣告一個抽象類(或介面),而不是直接使用new建立例項或者呼叫靜態方法。
總結
EF和EF Core可以為您節省大量時間,並使你更容易地關注領域模型而不是底層的資料庫問題。但是如果沒有正確使用它們,當你嘗試找出它們出現異常的原因時,它們也會讓你很頭痛。避免直接例項化和避免using塊都將使您的程式碼更容易使用。對於ASP.NET(Core)應用程式,應確保每個請求有且僅具有一個DbContext例項,而實現此目的的最佳方法是使用諸如Autofac之類的DI容器(或ASP. NET Core內建的 ServiceCollection)。
本文翻譯自 https://ardalis.com/avoid-wrapping-dbcontext-in-using/