基于 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 了。
相关文章
相关标签/搜索