避免用using包裝DbContext【翻譯】

   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)。

 

本文翻譯自 //ardalis.com/avoid-wrapping-dbcontext-in-using/