zoukankan      html  css  js  c++  java
  • 基于 EntityFramework 的数据库主从读写分离架构(2)- 改进配置和添加事务支持

        回到目录,完整代码请查看https://github.com/cjw0511/NDF.Infrastructure)中的目录:
         src NDF.Data.EntityFrameworkMasterSlaves
     
        上一回中(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     }

     

    再者,我们来聊聊数据库操作中的事务处理。

        我们都知道,数据库操作中的事务处理重要包括两大类:
        1、普通数据库操作事务处理,该类型由 DbTransaction 事务基类来控制;
        2、分布式事务,这类操作主要由组件 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 了。
  • 相关阅读:
    队列(顺序存储结构)
    2015计划
    iframe子窗口父窗口方法调用和元素获取
    Ajax关于重定向
    Java国际化资源文件的选择
    eclipse自动部署web应用程序到tomcat webapps
    从给定字符串结尾获取指定字节长度的字符串
    Winform的一些不知道啥东西
    C# 2008核心编程 2013-09-14
    C# 2008核心编程 2013-09-10
  • 原文地址:https://www.cnblogs.com/cjw0511/p/4398502.html
Copyright © 2011-2022 走看看