寫過上一篇關於EF Core中讀寫分離最佳實踐方式後,雖然在必定程度上改善了問題,可是在評論中有的指出更換到從數據庫,那麼接下來要進行插入此時又要切換到主數據庫,同時有的指出是否能夠進行底層無感知操做,這確實是個問題,本文繼續進行引路,進一步改善評論中問題的指出,至於實現更復雜的邏輯可自行實現,感謝大家很是棒的評論,使得本文由此而生。git
如評論前輩們指出在EF 6.x中咱們知道有IDbCommandInterceptor接口,咱們可對執行的SQL語句進行攔截,從而可自定義實現咱們想要的需求,雖然在EF Core中卻沒有攔截器特性的實現,可是針對此特性的實現DiagnosticSource記錄跟蹤類來實現等效於攔截器的實現,當前DiagnosticSource使用文檔還沒有完善,估計還得等待一段時間,接下來咱們來看看如何實現。在DiagnosticSource包中有DiagnosticListener類,咱們經過此類來跟蹤記錄,若是執行的EF Core包,那麼咱們將利用DiagnosticListener進行訂閱,訂閱到以後咱們拿到跟蹤命令,從而實現無感知更換數據庫,代碼以下:github
public class DbCommandInterceptor : IObserver<KeyValuePair<string, object>> { private const string masterConnectionString = "data source=WANGPENG;User Id=sa;Pwd=sa123;initial catalog=Demo1;integrated security=True;"; private const string slaveConnectionString = "data source=WANGPENG;User Id=sa;Pwd=sa123;initial catalog=Demo2;integrated security=True;"; public void OnCompleted() { } public void OnError(Exception error) { } public void OnNext(KeyValuePair<string, object> value) { if (value.Key == RelationalEventId.CommandExecuting.Name) { var command = ((CommandEventData)value.Value).Command; var executeMethod = ((CommandEventData)value.Value).ExecuteMethod; if (executeMethod == DbCommandMethod.ExecuteNonQuery) { ResetConnection(command, masterConnectionString); } else if (executeMethod == DbCommandMethod.ExecuteScalar) { ResetConnection(command, slaveConnectionString); } else if (executeMethod == DbCommandMethod.ExecuteReader) { ResetConnection(command, slaveConnectionString); } } } void ResetConnection(DbCommand command, string connectionString) { if (command.Connection.State == ConnectionState.Open) { if (!command.CommandText.Contains("@@ROWCOUNT")) { command.Connection.Close(); command.Connection.ConnectionString = connectionString; } } if (command.Connection.State == ConnectionState.Closed) { command.Connection.Open(); } } }
這裏存在一個問題,上述 command.CommandText.Contains("@@ROWCOUNT") 代碼,由於在進行添加操做時,會返回主鍵,那麼此時會進行查詢,因此暫時沒有更好的方式是確認主-從數據庫。數據庫
public class CommandListener : IObserver<DiagnosticListener> { private readonly DbCommandInterceptor _dbCommandInterceptor = new DbCommandInterceptor(); public void OnCompleted() { } public void OnError(Exception error) { } public void OnNext(DiagnosticListener listener) { if (listener.Name == DbLoggerCategory.Name) { listener.Subscribe(_dbCommandInterceptor); } } }
上述 DbLoggerCategory.Name 也就是 Microsoft.EntityFrameworkCore ,經過監控的包是Microsoft.EntityFrameworkCore,則進行訂閱,最後咱們在startup中進行註冊該監聽類。spa
DiagnosticListener.AllListeners.Subscribe(new CommandListener());
接下來咱們經過動態圖來看看最終實際效果,主-從複製依然是經過SQL Server發佈-訂閱的方式來同步數據。在控制器中,咱們只利用demo1上下文來添加和查詢數據,當查詢時更換到從數據庫,此時已經是無感知(請見上一篇),以下:code
[HttpGet("index")] public IActionResult Index() { var blogs = _demo1DbContext.Blogs.ToList(); return View(blogs); } [HttpGet("create")] public IActionResult CreateDemo1Blog() { var blog = new Blog() { Name = "demoBlog1", Url = "http://www.cnblogs.com/createmyslef" }; _demo1DbContext.Blogs.Add(blog); _demo1DbContext.SaveChanges(); return RedirectToAction(nameof(Index)); }
本文只是在上一篇的基礎上進一步改善了讀寫分離的操做,得益於github上有提出可經過跟蹤記錄來解決,經過跟蹤記錄從底層出發來完善讀寫分離操做,咱們可拿到底層實現的命令以及其餘和EF 6.x中利用攔截器等效,至於更加複雜的邏輯可自行實現。server