EntityFramework-based Master/Slave database read/write splitting architecture (2)-Improved Configuration and added transaction support, and database Master/Slave Separation
Go back to the directory and check the complete code.Directory in the (https://github.com/cjw0511/NDF.Infrastructure): src \ NDF. Data. EntityFramework \ MasterSlaves last in the middle (http://www.cnblogs.com/cjw0511/p/4398267.html), we briefly described the principle of implementing database read/write separation based on EF. Of course, this is just a simple demo-level implementation. In fact, in our work environment, the situation is far more complicated than this, for example, the configuration of a database connection is stored through the config file, and many transaction operations are required when database operations are performed. Today, let's talk about how to solve these problems.First, we will solve the problem of database connection string storage and configuration files.The Code is as follows:
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 SlaveConnectionString12 {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 // The Modification and judgment mechanism of the Connection string is improved here, make sure that the connection string used by <paramref name = "conn"/> is not equivalent to <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. connectionString = conn. connectionString; 67 68 DbConnectionStringBuilder B = factory. createConnectionStringBuilder (); 69 B. connectionString = connectionString; 70 71 return. equivalentTo (B); 72} 73}
Next, let's talk about the transaction processing in database operations.
As we all know, the transaction processing in database operations mainly includes two categories: 1. Normal database operation transaction processing, which is controlled by the DbTransaction transaction base class; 2. Distributed Transaction, this type of operation is mainly performed by the component System. transactions. The most common types include Transaction and TransactionScope. The significance and difference of common database transactions and distributed transactions, and how common transactions can be upgraded to distributed transactions are not described here. If you are interested, you can make up a new lesson. It should be noted that in the transaction operations of the database, many dbms do not support the same transaction to operate on different databases or servers. In addition, some dbms support the same transaction to operate multiple databases or servers (automatically promoted to distributed transactions), but msdtc is required. So here, my improved solution is to disable read/write splitting for all transaction operations, both common database transactions and distributed transactions, all database operations in the transaction (whether read or write, although this is not in line with the intention of "full read/write separation" to a certain extent, it solves the problem of Database Transaction compatibility, in addition, most project development projects do not have a majority of transaction operations) and all point to the Master server. To implement this, we only need to modify a small amount of code based on the encapsulation of the database server connection string:
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 SlaveConnectionString12 {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, DbCommandInterceptionCon Text <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 {3 7 this. updateConnectionStringIfNeed (context. database. connection, this. masterConnectionString); 38} 39} 40 41 private void UpdateToSlave (DbInterceptionContext interceptionContext) 42 {43 // determine whether the current session is in a distributed Transaction 44 bool isDistributedTran = Transaction. current! = Null & Transaction. Current. TransactionInformation. Status! = TransactionStatus. Committed; 45 foreach (var context in interceptionContext. DbContexts) 46 {47 // determine whether the context is in a common Database transaction 48 bool isDbTran = context. Database. CurrentTransaction! = Null; 49 50 // if the transaction is in a distributed transaction or normal transaction, the read/write splitting is disabled. All read/write operations in the transaction point to Master51 string connectionString = isDistributedTran | isDbTran? This. masterConnectionString: this. slaveConnectionString; 52 53 this. updateConnectionStringIfNeed (context. database. connection, connectionString); 54} 55} 56 57 58 // <summary> 59 // The Modification and judgment mechanism of the Connection string is improved here, make sure that the connection string used by <paramref name = "conn"/> is not equivalent to <paramref name = "connectionString"/>. 60 /// <para> when necessary, the connection is opened and closed and the ConnectionString is modified to reduce the performance consumption. </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. connectionString = conn. connectionString; 90 91 DbConnectionStringBuilder B = factory. createConnectionStringBuilder (); 92 B. connectionString = connectionString; 93 94 return. equivalentTo (B); 95} 96}
One thing to note about the above Code is that to obtain the normal Database Transaction Status of EF DbContext, you must obtain DbContext. database. currentTransaction attribute. Therefore, the UpdateConnectionString method is split into UpdateToMaster and UpdateToSlave.