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語句可能的問題:編程
與using語句和DbContext密切相關的問題是:若是有多個DbContext(由於該using語句一般會建立一個新實例)。
「若是您有多個DbContext嘗試使用相同的實體實例,那麼您將承受巨大的痛苦。」
考慮這個簡單的示例,它包含了一個Controller和一個service,二者同時在使用dbContext。app
假設在Index方法中,從數據庫中讀取 starship時,它的name屬性值爲「 Millenium Falcon」。下次訪問Index時,name屬性值是什麼?異步
SaveChanges()被調用時不執行任何操做。db實例在StarshipService中並未跟蹤該實體。因此名稱將保持不變。
那麼,咱們能夠這樣解決這個問題。讓咱們附加實體。這是更新後的service:函數
再次運行。如今Name的值是什麼?spa
SaveChanges()仍然不執行任何操做。實體的name被更新,該實體依然未跟蹤。
讓咱們再嘗試一次:翻譯
如今,它會起做用嗎?你以爲如何?對象
是的,如今name的值已更新爲「 Millenium Falcon *」,下次是「 Millenium Falcon **」,等等。blog
要正確地獲得預期的行爲,須要進行大量的嘗試工做。這段代碼是脆弱且重複的,更糟的是,具備隱藏的暫存依賴性。接口
那麼,MVC5/EF6的舊代碼中要怎麼使用Autofac呢?
安裝Autofac.MVC5 nuget軟件包。與.NET Framework nuget包不一樣,它不會添加一堆類,但你仍然須要將它們鏈接起來。
進到global.asax文件中更新Application_Start():
當你成功運行它,你還能夠把它們放置到一個模塊中,並把它的helper放到在App_Start目錄中。
這部分代碼中所作的事情有:
這可能也就須要用5分鐘。更新以後還會有混亂的代碼嗎?好了,服務如今看起來像這樣:
另外,控制器如今看起來像這樣:
請注意,這兩種類型如今都遵循顯式依賴項原則。同時注意到,在解耦以後的代碼沒徹底沒有new關鍵字,這與顯式依賴原則有關。
「方法和類應顯式要求(一般經過方法參數或構造函數參數)它們所須要的協做對象才能正常運行。」
不用對所引用對象的隱式依賴感到驚訝。若是須要引用某個類,應該在構造函數中引入它(注:依賴注入)。若是要將類進行解耦,,則應該聲明一個抽象類(或接口),而不是直接使用new建立實例或者調用靜態方法。
EF和EF Core能夠爲您節省大量時間,並使你更容易地關注領域模型而不是底層的數據庫問題。可是若是沒有正確使用它們,當你嘗試找出它們出現異常的緣由時,它們也會讓你很頭痛。避免直接實例化和避免using塊都將使您的代碼更容易使用。對於ASP.NET(Core)應用程序,應確保每一個請求有且僅具備一個DbContext實例,而實現此目的的最佳方法是使用諸如Autofac之類的DI容器(或ASP. NET Core內置的 ServiceCollection)。