基於 EntityFramework 的數據庫主從讀寫分離架構(2)- 改進配置和添加事務支持

     回到目錄,完整代碼請查看https://github.com/cjw0511/NDF.Infrastructure)中的目錄:
     src\ NDF.Data.EntityFramework\MasterSlaves
 
    上一回中( http://www.cnblogs.com/cjw0511/p/4398267.html),咱們簡單講述了基於 EF 來實現數據庫讀寫分離的原理。固然,這只是一個 demo 級別的簡單實現,實際上,在咱們工做環境中,碰到的狀況遠比這複雜多了,例如數據庫鏈接的配置是經過 config 文件來存儲、在進行數據庫操做時還須要附帶不少事務操做功能等等。今天咱們就來聊聊如何處理這些問題。
    
首先,咱們來解決數據庫鏈接字符串存儲與配置文件的問題
     代碼以下:
   
 1  public class DbMasterSlaveCommandInterceptor : DbCommandInterceptor
 2     {
 3         private Lazy<string> masterConnectionString = new Lazy<string>(() => ConfigurationManager.AppSettings["masterConnectionString"]);
 4         private Lazy<string> slaveConnectionString = new Lazy<string>(() => ConfigurationManager.AppSettings["slaveConnectionString"]);
 5  
 6         public string MasterConnectionString
 7         {
 8             get { return this.masterConnectionString.Value; }
 9         }
10  
11         public string SlaveConnectionString
12         {
13             get { return this.slaveConnectionString.Value; }
14         }
15  
16  
17         public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
18         {
19             this.UpdateConnectionStringIfNeed(interceptionContext, this.SlaveConnectionString);
20         }
21  
22         public override void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
23         {
24             this.UpdateConnectionStringIfNeed(interceptionContext, this.SlaveConnectionString);
25         }
26  
27         public override void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
28         {
29             this.UpdateConnectionStringIfNeed(interceptionContext, this.MasterConnectionString);
30         }
31  
32  
33         private void UpdateConnectionStringIfNeed(DbInterceptionContext interceptionContext, string connectionString)
34         {
35             foreach (var context in interceptionContext.DbContexts)
36             {
37                 this.UpdateConnectionStringIfNeed(context.Database.Connection, connectionString);
38             }
39         }
40  
41         /// <summary>
42         /// 此處改進了對鏈接字符串的修改判斷機制,確認只在 <paramref name="conn"/> 所使用的鏈接字符串不等效於 <paramref name="connectionString"/> 的狀況下才須要修改。
43         /// </summary>
44         /// <param name="conn"></param>
45         /// <param name="connectionString"></param>
46         private void UpdateConnectionStringIfNeed(DbConnection conn, string connectionString)
47         {
48             if (this.ConnectionStringCompare(conn, connectionString))
49             {
50                 ConnectionState state = conn.State;
51                 if (state == ConnectionState.Open)
52                     conn.Close();
53  
54                 conn.ConnectionString = connectionString;
55  
56                 if (state == ConnectionState.Open)
57                     conn.Open();
58             }
59         }
60  
61         private bool ConnectionStringCompare(DbConnection conn, string connectionString)
62         {
63             DbProviderFactory factory = DbProviderFactories.GetFactory(conn);
64  
65             DbConnectionStringBuilder a = factory.CreateConnectionStringBuilder();
66             a.ConnectionString = conn.ConnectionString;
67  
68             DbConnectionStringBuilder b = factory.CreateConnectionStringBuilder();
69             b.ConnectionString = connectionString;
70  
71             return a.EquivalentTo(b);
72         }
73     }

 

再者,咱們來聊聊數據庫操做中的事務處理。html

    咱們都知道,數據庫操做中的事務處理重要包括兩大類:
    一、普通數據庫操做事務處理,該類型由 DbTransaction 事務基類來控制;
    二、分佈式事務,這類操做主要由組件 System.Transactions 來控制,最經常使用的類型包括 Transaction 和 TransactionScope。
    
    具體涉及到普通數據庫事務和分佈式事務的意義和區別、普通事務如何會提高爲分佈式事務等知識點,這裏就不贅述了,有興趣的同窗能夠另行補課。
    這裏須要說明的是,在數據庫的事務操做中,不少 dbms 是不支持同一個事務操做不一樣的數據庫或服務器的。另外某些 dbms 支持同一個事務操做多個數據庫或服務器(自動提高爲分佈式事務),可是須要 msdtc 的支持。
    
    因此在這裏,我改進的方案是,凡是全部的事務操做,無論是普通數據庫事務,仍是分佈式事務,都「禁用」讀寫分離,即將全部的在事務內的數據庫操做(無論是讀仍是寫,雖然這必定程度上不符合「徹底的讀寫分離」的本意,可是解決了數據庫事務兼容性的問題,並且大多數項目開發中,包含事務的操做不佔多數),都指向 Master 服務器。實際上基於咱們前面對數據庫服務器鏈接字符串的封裝,要實現這一點,只須要改動少許代碼,以下:
 1 public class DbMasterSlaveCommandInterceptor : DbCommandInterceptor
 2     {
 3         private Lazy<string> masterConnectionString = new Lazy<string>(() => ConfigurationManager.AppSettings["masterConnectionString"]);
 4         private Lazy<string> slaveConnectionString = new Lazy<string>(() => ConfigurationManager.AppSettings["slaveConnectionString"]);
 5  
 6         public string MasterConnectionString
 7         {
 8             get { return this.masterConnectionString.Value; }
 9         }
10  
11         public string SlaveConnectionString
12         {
13             get { return this.slaveConnectionString.Value; }
14         }
15  
16  
17         public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
18         {
19             this.UpdateToSlave(interceptionContext);
20         }
21  
22         public override void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
23         {
24             this.UpdateToSlave(interceptionContext);
25         }
26  
27         public override void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
28         {
29             this.UpdateToMaster(interceptionContext);
30         }
31  
32  
33         private void UpdateToMaster(DbInterceptionContext interceptionContext)
34         {
35             foreach (var context in interceptionContext.DbContexts)
36             {
37                 this.UpdateConnectionStringIfNeed(context.Database.Connection, this.MasterConnectionString);
38             }
39         }
40  
41         private void UpdateToSlave(DbInterceptionContext interceptionContext)
42         {
43             // 判斷當前會話是否處於分佈式事務中
44             bool isDistributedTran = Transaction.Current != null && Transaction.Current.TransactionInformation.Status != TransactionStatus.Committed;
45             foreach (var context in interceptionContext.DbContexts)
46             {
47                 // 判斷該 context 是否處於普通數據庫事務中
48                 bool isDbTran = context.Database.CurrentTransaction != null;
49  
50                 // 若是處於分佈式事務或普通事務中,則「禁用」讀寫分離,處於事務中的全部讀寫操做都指向 Master
51                 string connectionString = isDistributedTran || isDbTran ? this.MasterConnectionString : this.SlaveConnectionString;
52  
53                 this.UpdateConnectionStringIfNeed(context.Database.Connection, connectionString);
54             }
55         }
56  
57  
58         /// <summary>
59         /// 此處改進了對鏈接字符串的修改判斷機制,確認只在 <paramref name="conn"/> 所使用的鏈接字符串不等效於 <paramref name="connectionString"/> 的狀況下才須要修改。
60         /// <para>同時,在必要的狀況下才會鏈接進行 Open 和 Close 操做以及修改 ConnectionString 處理,減小了性能的消耗。</para>
61         /// </summary>
62         /// <param name="conn"></param>
63         /// <param name="connectionString"></param>
64         private void UpdateConnectionStringIfNeed(DbConnection conn, string connectionString)
65         {
66             if (this.ConnectionStringCompare(conn, connectionString))
67             {
68                 this.UpdateConnectionString(conn, connectionString);
69             }
70         }
71  
72         private void UpdateConnectionString(DbConnection conn, string connectionString)
73         {
74             ConnectionState state = conn.State;
75             if (state == ConnectionState.Open)
76                 conn.Close();
77  
78             conn.ConnectionString = connectionString;
79  
80             if (state == ConnectionState.Open)
81                 conn.Open();
82         }
83  
84         private bool ConnectionStringCompare(DbConnection conn, string connectionString)
85         {
86             DbProviderFactory factory = DbProviderFactories.GetFactory(conn);
87  
88             DbConnectionStringBuilder a = factory.CreateConnectionStringBuilder();
89             a.ConnectionString = conn.ConnectionString;
90  
91             DbConnectionStringBuilder b = factory.CreateConnectionStringBuilder();
92             b.ConnectionString = connectionString;
93  
94             return a.EquivalentTo(b);
95         }
96     }

 

    關於上面的代碼,須要說明的一點是,由於要獲取 EF DbContext 的普通數據庫事務狀態,必須得拿到 DbContext.Database.CurrentTransaction 屬性,因此將 UpdateConnectionString 方法拆分紅 UpdateToMaster 和 UpdateToSlave 了。
相關文章
相關標籤/搜索