EF架构~通过EF6的DbCommand拦截器来实现数据库读写分离~续~添加事务机制
上一讲中简单介绍了一个EF环境下通过DbCommand拦截器来实现SQLSERVER的读写分离,只是一个最简单的实现,而如果出现事务情况,还是会有一些问题的,因为在拦截器中我们手动开启了Connection链接,而在一个WEB请求时,如果你的一个变量即用到了read库又用到了write库,就会导致到sqlserver端的spid(system process id,系统进程ID,sqlserver里可能是某个数据库进程序的ID)发生变化 ,而对于这种变化,原本是本地的事务就会自动提升为分布式事务,对MSDTC不了解的同学,可能看我的相关文章,所以,我们使用拦截实现读写分离后,在程序里,你的读和写的仓储对象要分别定义,不能共享,而且,你在事务里所以写的仓储对象都要使用同一个数据上下文!
当你按着我说的做后,本地事务就不会提升为msdtc了,如图:
今天我在DbCommand拦截器进行了优化,下面共享一下代码,如是测试不是真实一项目代码
/// <summary> /// SQL命令拦截器 /// </summary> public class SqlCommandInterceptor : DbCommandInterceptor { /// <summary> /// 读库,从库集群,写库不用设置走默认的EF框架 /// </summary> string readConn = System.Configuration.ConfigurationManager.AppSettings["readDb"] ?? string.Empty; private string GetReadConn() { var readArr = readConn.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); var resultConn = string.Empty; if (readArr != null && readArr.Any()) { resultConn = readArr[Convert.ToInt32(Math.Floor((double)new Random().Next(0, readArr.Length)))]; } return resultConn; } //linq to entity生成的update,delete public override void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext) { base.NonQueryExecuting(command, interceptionContext);//update,delete等写操作直接走主库 } /// <summary> /// 执行sql语句,并返回第一行第一列,没有找到返回null,如果数据库中值为null,则返回 DBNull.Value /// </summary> /// <param name="command"></param> /// <param name="interceptionContext"></param> public override void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext) { if (!string.IsNullOrWhiteSpace(GetReadConn()))//如果配置了读写分离,就去实现 { if (!command.CommandText.StartsWith("insert", StringComparison.InvariantCultureIgnoreCase)) { command.Connection.Close(); command.Connection.ConnectionString = GetReadConn(); command.Connection.Open(); } } base.ScalarExecuting(command, interceptionContext); } /// <summary> /// linq to entity生成的select,insert /// 发送到sqlserver之前触发 /// warning:在select语句中DbCommand.Transaction为null,而ef会为每个insert添加一个DbCommand.Transaction进行包裹 /// </summary> /// <param name="command"></param> /// <param name="interceptionContext"></param> public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext) { if (!string.IsNullOrWhiteSpace(GetReadConn()))//如果配置了读写分离,就去实现 { if (!command.CommandText.StartsWith("insert", StringComparison.InvariantCultureIgnoreCase)) { command.Connection.Close(); command.Connection.ConnectionString = GetReadConn(); command.Connection.Open(); } } base.ReaderExecuted(command, interceptionContext); } /// <summary> /// 发送到sqlserver之后触发 /// </summary> /// <param name="command"></param> /// <param name="interceptionContext"></param> public override void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext) { base.ReaderExecuted(command, interceptionContext); } }
运行程序可以设置一些测试代码

public ActionResult Index() { IUnitOfWork db = new backgroundEntities(); IRepository<WebManageUsers> readUser = new BackgroundRepositoryBase<WebManageUsers>(); var a = readUser.GetModel().ToList();//读库 using (var trans = new TransactionScope())//事务写库 { IRepository<WebManageUsers> userWrite = new BackgroundRepositoryBase<WebManageUsers>(db); IRepository<WebManageMenus> menuWrite = new BackgroundRepositoryBase<WebManageMenus>(db); var entity = new WebManageUsers { WebSystemID = 0, CreateDate = DateTime.Now, DepartmentID = 3, Description = "", Email = "", LoginName = "test", Mobile = "", Operator = "", Password = "", RealName = "test", Status = 1, UpdateDate = DateTime.Now, }; var entity2 = new WebManageMenus { ParentID = 1, About = "", LinkUrl = "", MenuLevel = 1, MenuName = "test", Operator = "", SortNumber = 1, Status = 1, UpdateDate = DateTime.Now, }; userWrite.Insert(entity); menuWrite.Insert(entity2); trans.Complete(); } return View(a); }
最后的结果就是我们想要的,这里说明一点,仓储大步的读写分离没有数据库压力这块的考虑,只是随机去访问某个读库。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示
2012-01-08 架构那些事~MVC系统架构中哪些应该进行抽象