在EntityFramework6中管理DbContext的正確方式——2DbContext的預設行為(外文翻譯)

来源:http://www.cnblogs.com/JefferyZhang/archive/2016/05/06/5461455.html
-Advertisement-
Play Games

(譯者註:使用EF開發應用程式的一個難點就在於對其DbContext的生命周期管理,你的管理策略是否能很好的支持上層服務 使用獨立事務,使用嵌套事務,並行執行,非同步執行等需求? Mehdi El Gueddari對此做了深入研究和優秀的工作並且寫了一篇優秀的文章,現在我將其翻譯為中文分享給大家。由於 ...


(譯者註:使用EF開發應用程式的一個難點就在於對其DbContext的生命周期管理,你的管理策略是否能很好的支持上層服務 使用獨立事務,使用嵌套事務,並行執行,非同步執行等需求? Mehdi El Gueddari對此做了深入研究和優秀的工作並且寫了一篇優秀的文章,現在我將其翻譯為中文分享給大家。由於原文太長,所以翻譯後的文章將分為四篇。你看到的這篇就是是它的第二篇。原文地址:http://mehdi.me/ambient-dbcontext-in-ef6/)

DbContext的預設行為

通常來說,DbContext的預設行為可以被描述為:“預設情況下就能做正確的事”。

下麵是你應該記在腦海裡面的幾個關於EntityFramework的重要行為。這個列表描述了EF訪問SqlServer的行為。用其它的資料庫可能會略有差異。

DbContext不是線程安全的

你千萬不要從多個線程同時去訪問DbContext派生類實例。這可能導致將多個查詢通過一個相同的資料庫連接被同時發送了出去——它將破壞DbContext維護的一級緩存的狀態——它們被用來提供標識映射(Identity Map),變更追蹤和工作單元的功能。

在一個多線程應用程式中,你必須為每一個線程創建一個獨立的DbContext派生類實例。

問題來了,如果DbContext不是線程安全的,那麼它怎麼支持EF6的非同步功能呢?其實很簡單:只需要保證在任何時刻只有一個非同步操作被執行(就像EF的支持非同步模式的規範描述的那樣)。如果你嘗試在同一個DbContext實例上併發的執行多個操作,比如通過DbSet<T>.ToListAsync()方法併發地執行多個查詢語句,你將會得到一個帶有下麵消息的NotSupportedException

  A second operation started on this context before a previous asynchronous operation completed. Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context. Any instance members are not guaranteed to be thread safe.

EF的非同步功能是為了支持非同步編程模型,而不是併發編程模型。

當且僅當SaveChanges()方法被調用的時候,修改才會被持久化

任何對實體的修改,包括更新,插入或者刪除,當且僅當DbContext.SaveChanges()被調用的時候才會被持久化到資料庫。如果DbContext實例在SaveChanges()方法被調用之前就被釋放掉了,那麼這些更新操作,插入操作,刪除操作沒有一條能持久化到底層資料庫。

下麵是用EF來實現一個業務事務的規範方式:

           

  using (var context = new MyDbContext(ConnectionString))

            {

                /* 

                * 業務邏輯放在這兒. 通過context添加,修改,刪除數據。

                * 

                * 拋出任何異常就可以回滾所有變化。

                * 

                * 直到業務事務完成,否則不能調用SaveChanges()方法

                * 也就是說不能部分或者中間保存。

                * 每一個業務事務只能剛好調用一次SaveChanges()方法 。

                *

                * 如果你發現你自己需要在一個業務事務裡面多次調用

                * SaveChanges()方法,那就意味著你在一個服務方法

                * 裡面實現多個業務事務。這絕對是災難的“必備良藥”。 

                * 調用你的服務的客戶端會很自然的假定你的服務方法

                * 以原子的行為提交或者回滾——但你的服務卻可能

                * 部分提交,讓系統處於一個不一致的狀態。 

                *

                * 在這種情況下,將你的服務方法重構成多個服務方法——

                * 每一個服務方法剛好實現了剛好一個業務事務。                                    

                */

                [...]

                // 完成業務事務並且持久化所有變化 。

                context.SaveChanges();

                // 在這行代碼之後變化不可能回滾了。

                // context.SaveChanges()應當是任何業務事務

                // 的最後一行代碼。

            }

 

NHibernate用戶註意事項

如果你擁有NHibernate背景,那麼可以告訴你的是EF將變化持久化到資料庫的方式是它與NHibernate的最大不同。

NHibernate,Session操作預設情況下處於AutoFlush模式。在這種模式下,Session將在執行任何‘select’操作之前自動將所有變化持久化到資料庫——確保已持久化到資料庫的實體和它們在Session中的記憶體狀態保持一致。對NHibernate來說,EF的預設行為相當於將Session.FlushMode設置為Never

EF的這個行為可能會導致一些微妙的bug——查詢意外的返回過時的或者不正確的數據。預設情況下NHibernate是絕不可能出現這種情況的。但從另外一方面來說,這卻又極大的簡化了資料庫事務管理的問題。

NHibernate中最棘手的問題之一就是正確的管理資料庫事務。由於NHibernateSession可以在它的整個生命周期中的任何時間點自動地將未持久化的變化持久化到資料庫,並且可能在一個業務事務裡面持久化多次——這兒沒有一個定義良好的點或者方法來開啟資料庫事務以確保所有的修改以原子的行為提交或者回滾。

NHibernate中正確管理資料庫事務的唯一可靠方法就是將你的所有服務方法打包在一個顯式資料庫事務中。這就是大部分基於NHibernate的應用程式的處理方式。

這種方式的負面效應就是它要求打開一個資料庫連接和事務的時間比實際需要的要更長——因此增加了資料庫鎖的競爭和資料庫死鎖發生的可能性。開發者也很容易不經意的執行一個長時間計算或者一個遠程服務方法的調用而沒有意識到甚至根本就不知道他們是在一個資料庫事務打開的上下文中。

EF的方式——只有SaveChanges()方法必須被打包在一個顯式資料庫事務中(當然使用一個REPEATABLE READ 或者SERIALIZABLE隔離級別的情況例外),保證了資料庫連接和事務保持儘可能的短暫。 

使用自動提交事務(AutoCommit transaction)來執行讀取操作

DbContext不支持打開一個顯式事務來執行讀取操作。它依賴於SQL Server的自動提交事務(Autocommit Transaction) (或者 隱式事務(Implicit Transaction)——如果你啟用了它們的話,但那相對來說不是常見的操作)。自動提交事務(或者隱式事務)將會使用資料庫引擎被配置的預設事務隔離級別(對SQL Server來說就是READ COMMITTED)。

如果你已經工作有一段時間,尤其是如果你以前使用過NHibernate,那麼你可能聽說過“自動提交事務(或者隱式事務)是糟糕的”。實際上,依賴於自動提交事務的寫操作可能在性能上產生災難性影響

但對於讀操作來說情況就大不一樣了。你可以跑下麵的SQL腳本親自去看看。對select語句來說,自動提交事務或者隱式事務都不會有任何明顯的性能影響。

 

/* 

 * 用自動提交事務,隱式事務,顯式事務分部執行10000 

 * 次select查詢. 

 * 

 * 這些腳本假定資料庫包含一張Users表,它有一個列名為Id

 * 類型為INT的列。

 * 

 * 如果你在SQL Server Management Studio裡面運行的話

 * 右鍵查詢視窗,進入查詢選項 -> 點擊結果並勾選

 * “執行後放棄結果”。否則你的測試結果將會被網格的

 * 刷新驗證影響

 */

---------------------------------------------------

-- 自動提交事務

-- 6 秒

DECLARE @i INT  

SET @i = 0

WHILE @i < 100000  

    BEGIN 

        SELECT  Id

        FROM    dbo.Users

        WHERE   Id = @i

        SET @i = @i + 1

    END

---------------------------------------------------

-- 隱式提交事務

-- 6 秒

SET IMPLICIT_TRANSACTIONS ON  

DECLARE @i INT  

SET @i = 0  

WHILE @i < 100000  

    BEGIN 

        SELECT  Id

        FROM    dbo.Users

        WHERE   Id = @i

        SET @i = @i + 1

    END

COMMIT;  

SET IMPLICIT_TRANSACTIONS OFF

----------------------------------------------------

-- 顯示事務

-- 6 秒

DECLARE @i INT  

SET @i = 0  

BEGIN TRAN  

WHILE @i < 100000  

    BEGIN

        SELECT  Id

        FROM    dbo.Users

        WHERE   Id = @i

        SET @i = @i + 1

    END

COMMIT TRAN  

 

很顯然,如果你需要用一個比預設READ COMMITTED更高的隔離級別的話,那麼所有讀操作都將是顯式資料庫事務的一部分。在那種情況下,你需要自己開啟事務——EF將不會為你做這個。但這通常只會為指定的業務事務做特別處理。EF的預設設置能適合大部分業務事務。

使用顯式事務來執行寫操作

EF通過DbContext.SaveChanges()方法自動地將所有操作打包在一個顯式資料庫事務裡面——以確保應用在context的所有修改要麼完全提交要麼完全回滾。

EF寫操作使用資料庫引擎配置的預設事務隔離級別(對SQL Server來說就是READ COMMITTED)。

NHibernate用戶註意事項

這是EFNHibernate之間的另一個很大的不同點。在NHibernate中,資料庫事務完全掌握在開發者手中。NHibernateSession永遠不會自動地打開一個顯式資料庫事務。

你可以重寫EF的預設行為並控制資料庫事務範圍和隔離級別

  

using (var context = new MyDbContext(ConnectionString))
   {
       using (var transaction =context.BeginTransaction(IsolationLevel.RepeatableRead))
       {
              [...]
              context.SaveChanges();
              transaction.Commit();
        }
   }

 

手動控制資料庫事務範圍的一個非常明顯的副作用就是你必須在整個事務範圍中讓資料庫連接和事務保持打開。

你應當儘可能的讓這個事務範圍生命周期短暫。打開一個資料庫事務運行太長時間可能會對應用程式的性能和可擴展性有非常巨大的影響。特別指出的是,儘量不要再一個顯示事務範圍內調用其它的服務方法——它們可能執行長時間運行的操作而沒有意識到它們是在一個打開的資料庫事務內被調用。

EF沒有內建的方式來重寫用作自動提交事務和自動顯式事務的預設隔離級別

就像上面提到的,EF依賴自動提交事務來執行讀操作並且當調用SaveChanges()方法的時候自動以資料庫配置的預設隔離級別開啟一個顯式事務。

很不幸的是沒有內建的方式來重寫這些隔離級別,如果你想用另一個隔離級別,你必須自己開啟和管理資料庫事務。

通過DbContext打開的資料庫連接自動加入一個周圍環境的TransactionScope

另外,你也可以用TransactionScope來控制事務範圍和隔離級別。EF打開的資料庫連接自動加入周圍環境的TransactionScope

EF6之前,使用TransactionScope是唯一可靠的方式來控制資料庫事務範圍和隔離級別。

在實踐中,除非你真的需要一個分散式事務,否則儘量避免使用TransactionScopeTransactionScope,通常指分散式事務,對大部分應用程式來說都是不必要的。並且它們通常會帶來比它們解決的問題都要更多的問題。如果你真的需要一個分散式事務的話,可以查看EF文檔章節——EF中使用TransactionScope

 

DbContext實例應當被釋放掉(但是如果沒有釋放掉,也可能沒事)

DbContext實現了IDisposable介面,因此一旦它們不需要了就應當儘快釋放。

然而在實踐中,除非你選擇顯式控制DbContext使用的資料庫連接或者事務,否則不調用DbContext.Dispose()方法也不會引起任何問題——就像Diego Vega,一個EF團隊成員解釋的那樣

這是一個好消息——因為你會發現很多代碼不能正確地釋放DbContext實例。尤其是那些嘗試用DI容器來管理DbContext實例生命周期的情況——實際情況比聽起來要棘手得多。

一個DI容器,比如說StructureMap,它不支持釋放它創建的組件。因此,如果你依賴StructureMap來創建DbContext實例,那麼它們將不會被釋放掉——不管你為它們設置的什麼生命周期方式。使用像這樣的DI容器來管理可釋放組件的唯一正確方式就是複雜你的DI配置並且使用一個嵌套依賴註入容器——就像Jeremy Miller描述的那樣


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 菜鳥一枚,入園已有兩年三個月,這還是第一次寫博客,請各位大神斧正。 這是我寫的一個工具類,通常我們從資料庫查詢到一個 DataReader 或者是 一個 Table , 想要轉換成 一個 list 或者是 一個model 的話 , 一般情況下是使用foreach 迴圈reader或是table的ro ...
  • Github開源聲明 本網站的代碼開源,開源的目的如下 技術分享 希望業內同行貢獻代碼 希望能夠讓網站更加安全 開源地址: "CodeSnippet開源地址" 關於代碼貢獻 任何人都可以貢獻代碼,一般在 1 3個工作日內會確認合併 代碼請進行測試後提交。 現在需要如下的代碼貢獻 移動端自適應和響應式 ...
  • WCF的宿主可以是 Windows 服務、COM+應用程式、WAS(Windows Activation Services,Windows進程激活服務)或IIS、Windows應用程式,或簡單的控制台應用程式及任何.net程式。 ...
  • 做了1年多了C#,發現些項目過程中很多基礎東西都不是很清晰,基礎不夠牢固。現在開始複習基礎知識並做重點記錄 方法需要被重寫的時候,可以在方法前加入virtual使方法變成虛方法。 這樣我們可以重新寫個方法對虛方法進行重寫需要加上override。 註意:成員欄位和靜態函數都不能聲明為virtual, ...
  • 回到目錄 多看幾篇 之所以寫這篇文章完全是因為最近在研究FastDFS這個分散式的文件存儲系統,當然這不是我第一次研究它了,就像我們去看一本書,我們不會只看一篇,而是一次次,一篇篇,每看一次會有新的收穫,而研究技術,框架也是一樣,每研究一次,同樣會有不同層次的收穫,這次主要把fastDFS的集群就配 ...
  • 一些項目中,會涉及到事務的寫法,比如訂單相關,訂單成功,會涉及到產品的庫存和賬戶金額的一些信息變動,當然,如果整個流程成功,那是沒什麼問題,關鍵是如果中間某一步驟出現bug了,那之前已執行的一些變動就要回滾回去,所以就不可避免的用到事務的寫法。以前只是在資料庫中會涉及到事務寫法 最近做一些財務方面的 ...
  • 可以在 程式包管理器控制臺中輸入PM> Install-Package NPOI會下載最新版本NPOI----------------------------引用了NPOI-------------------------------- public static void Export() { s... ...
  • C#
    泛型:通過參數化類型來實現在同一份代碼上操作多種數據類型。利用“參數化類型”將類型抽象化,從而實現靈活的復用。 例子代碼: class Program { static void Main(string[] args) { ... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...