前言
写过上一篇关于 EF Core 中读写分离最佳实践方式后, 虽然在一定程度上改善了问题, 但是在评论中有的指出更换到从数据库, 那么接下来要进行插入此时又要切换到主数据库, 同时有的指出是否可以进行底层无感知操作, 这确实是个问题, 本文继续进行引路, 进一步改善评论中问题的指出, 至于实现更复杂的逻辑可自行实现, 感谢你们非常棒的评论, 使得本文由此而生.
EF Core 读写分离进一步完善
如评论前辈们指出在 EF 6.x 中我们知道有 IDbCommandInterceptor 接口, 我们可对执行的 SQL 语句进行拦截, 从而可自定义实现我们想要的需求, 虽然在 EF Core 中却没有拦截器特性的实现, 但是针对此特性的实现 DiagnosticSource 记录跟踪类来实现等效于拦截器的实现, 当前 DiagnosticSource 使用文档尚未完善, 估计还得等待一段时间, 接下来我们来看看如何实现. 在 DiagnosticSource 包中有 DiagnosticListener 类, 我们通过此类来跟踪记录, 如果执行的 EF Core 包, 那么我们将利用 DiagnosticListener 进行订阅, 订阅到之后我们拿到跟踪命令, 从而实现无感知更换数据库, 代码如下:
- 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 中进行注册该监听类.
DiagnosticListener.AllListeners.Subscribe(new CommandListener());
接下来我们通过动态图来看看最终实际效果, 主 - 从复制依然是通过 SQL Server 发布 - 订阅的方式来同步数据. 在控制器中, 我们只利用 demo1 上下文来添加和查询数据, 当查询时更换到从数据库, 此时已是无感知 (请见上一篇), 如下:
- [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 中利用拦截器等效, 至于更加复杂的逻辑可自行实现.
来源: https://www.cnblogs.com/CreateMyself/p/9261435.html