回到目錄,完整代碼請查看(
https://github.com/cjw0511/NDF.Infrastructure)中的目錄:
src\ NDF.Data.EntityFramework\MasterSlaves
在本上的上一篇博文中(基於 EntityFramework 的數據庫主從讀寫分離服務插
件,
http://www.cnblogs.com/cjw0511/p/4391092.html),概述性的介紹了自己基於 EF6 寫的一個用於數據庫主從讀寫分離服務的一個插件。因為時間關系,上一篇博文只講到了該插件的功能概述和基本用法。今天正好有空,就花點時間構思了這篇博文,和大家一起來交流一下,本人基於 EF6 的數據庫主從讀寫分離服務插件是如何構思、設計以及編碼實現的。
首先,在繼續進行本文的后續內容閱讀之前,應該先對一些相關知識點有所了解,包括但不限於如下幾個方面:
1、ADO.NET 基礎知識;
2、EntityFramework 的功能和基本用法;
3、數據庫讀寫分離的概念(
http://baike.baidu.com/view/3372624.htm);
4、數據庫主從復制功能的基本配置(不同數據庫系統配置方式不同);
以上幾點是閱讀和實現本文所述的數據庫主從讀寫分離的基礎知識,本文就不做詳細贅述了,如有想在這些方面另需了解的同學,請自行補課。
好了,接下來進入正題吧。
我們都知道,在應用程序開發中,要實現數據庫的主從讀寫分離操作,也就是讓所有的數據變更操作請求都指向 Master(主) 服務器,而所有的數據查詢請求都指向 Slave(從)服務器,其根本就在於構建 ADO.NET 的 DbConnection 連接時,ConnectionString 的指向不同。在以前,沒有用 ORM 框架時,我們可能通過自定義一些諸如 DataAccessHelper 的工具類,在工具類型定義兩大類型的 API,其一為數據庫變更操作,其二為數據查詢操作。在數據庫變更操作中,生成的 DbConnection 的連接地址指向 Master 服務器;在數據查詢操作時,生成的 DbConnection 指向另一台 Slave 服務器。
而如今,由於需要分離關注點、提高代碼復用性和編碼工作效率、降低程序員對 sql 的知識依賴等諸多原因,我們用上了 EntityFramework、NHibernate 等類似的 ORM 框架。實際上,在 ORM 框架中,要實現數據庫讀寫分離,和我們早期直接用 ADO.NET 來實現該功能的原理是一樣的。而不同之處,主要就在於,ORM 框架一般提供了AOP 架構的切面注入方式,讓我們可以在某個關注點上添加我們自定義的操作。
在 EntityFramework 的 6.1 版本中,新增了一個命名空間 System.Data.Entity.Infrastructure.Interception,該命名空間主要就是提供了一個面向切面的程序注入功能,讓我們可以在 EF 最終向數據庫提交 DbCommand 執行請求前后,攔截到該動作並執行我們自定義的其他附加動作。基本的做法為:
1、自定義一個 System.Data.Entity.Infrastructure.Interception.DbCommandInterceptor 類型的子類;
2、在該自定義類型中重寫方法 ReaderExecuting、ReaderExecuted、ScalarExecuting、ScalarExecuted、NonQueryExecuting、NonQueryExecuted,方法體內即可實現自己對查詢命令、更改命令的執行前后附加動作;
3、通過 System.Data.Entity.Infrastructure.Interception.DbInterception.Add 方法,將該自定義的類型實例添加至 EF 執行上下文中;
完成以上幾個步驟后,EF 即可在每次執行增刪改或查詢命令前后,額外我們我們定義的其他動作。
廢話不多說,下面貼上一段代碼,用於表示實現 EF 讀寫分離效果的 DbCommandInterceptor 基本實現:
1 public class DbMasterSlaveCommandInterceptor : DbCommandInterceptor 2 { 3 private string masterConnectionString = "server=192.168.0.99;port=3306;user id=root;password=123456;persistsecurityinfo=True;database=testdb"; 4 private string slaveConnectionString = "server=192.168.0.101;port=3306;user id=root;password=123456;persistsecurityinfo=True;database=testdb"; 5 public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext) 6 { 7 this.UpdateConnectionString(interceptionContext, this.slaveConnectionString); 8 } 9 public override void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext) 10 { 11 this.UpdateConnectionString(interceptionContext, this.slaveConnectionString); 12 } 13 public override void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext) 14 { 15 this.UpdateConnectionString(interceptionContext, this.masterConnectionString); 16 } 17 private void UpdateConnectionString(DbInterceptionContext interceptionContext, string connectionString) 18 { 19 foreach (var context in interceptionContext.DbContexts) 20 { 21 this.UpdateConnectionString(context.Database.Connection, connectionString); 22 } 23 } 24 private void UpdateConnectionString(DbConnection conn, string connectionString) 25 { 26 ConnectionState state = conn.State; 27 if (state == ConnectionState.Open) 28 conn.Close(); 29 conn.ConnectionString = connectionString; 30 if (state == ConnectionState.Open) 31 conn.Open(); 32 } 33 }
接着,在 Global.asax 的啟動代碼中將該 類型的實體注入 EF 全局執行上下文中。
1 public class MyHttpApplication : HttpApplication 2 { 3 protected void Application_Start() 4 { 5 DbInterception.Add(new DbMasterSlaveCommandInterceptor()); 6 } 7 }
怎么樣,原理是不是很簡單?當然,如果想要實現一些豐富的配置和擴展功能,就還需要很多其他的代碼了,關於這些本人將會在后續文章中逐步介紹!