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);
        }

最后的结果就是我们想要的,这里说明一点,仓储大步的读写分离没有数据库压力这块的考虑,只是随机去访问某个读库。

回到目录

时间: 2024-10-29 08:57:54

EF架构~通过EF6的DbCommand拦截器来实现数据库读写分离~续~添加事务机制的相关文章

EF架构~通过EF6的DbCommand拦截器来实现数据库读写分离~终结~配置的优化和事务里读写的统一

回到目录 本讲是通过DbCommand拦截器来实现读写分离的最后一讲,对之前几篇文章做了一个优化,无论是程序可读性还是实用性上都有一个提升,在配置信息这块,去除了字符串方式的拼接,取而代之的是section数组,这样在修改配置时更加清晰了:而实用性上,彻底改变了读和写不能共用一个仓储对象的缺点,并且在一个事务里可以读写并存,并为了数据的一致性,使事务里的curd操作指向主库,这一点很重要! 前几篇文章的目录 EF架构~通过EF6的DbCommand拦截器来实现数据库读写分离~再续~添加对各只读服

EF架构~通过EF6的DbCommand拦截器来实现数据库读写分离~再续~添加对各只读服务器的心跳检测

回到目录 上一讲中基本实现了对数据库的读写分离,而在选择只读数据库上只是随机选择,并没有去检测数据库服务器是否有效,如服务器挂了,SQL服务停了,端口被封了等等,而本讲主要对以上功能进行一个实现,并对配置文件也进行了一些优化,让它更好的支持多个数据库服务器,分别配置各个的账号和密码及数据库服务端口等等,接下来,就来看一下主要的代码吧. 一 配置文件 <!-- ef实现对sql读写分离的配置,sqlserver端采用发布与订阅实现 --> <add key="readDb&quo

EF架构~通过EF6的SQL命名拦截器来实现数据库读写分离

回到目录 前几天看了一个基于sqlserver的负载均衡与读写分离的软件Moebius,实现的方式还是不错的,这使得用sqlserver数据库的同学时有机会对数据库进行更有效的优化了 看着人有做的东西,自己也想用EF来实现一个读写分离,所以就有了本篇文章,仓储大叔读写分离的思路是: 1 用sqlserver自带的发布.订阅实现主,从数据库的结构,同步这事由sql帮我们完成 2 配置文件建立几个供只读的数据库连接串 3 建立SQL命令拦截器 4 修改大叔的DbContextRepository基数

大型网站架构演进(5)数据库读写分离

在使用缓存后,使大部分的数据读操作访问都可以不通过数据库就能完成,但是仍有一部分读操作(包括未命中缓存的,和缓存过期的)和全部的写操作需要访问数据库,当网站的访问量继续增加后,数据库会因为负载压力过高导致成为网站的性能瓶颈. 目前大部分的主流数据库都提供了主从热血功能,通过配置两台数据库的主从关系,可以将一台数据库服务器的数据同步到另一台服务器上,网站利用数据库的这一功能,可以实现数据库的读写分离,从而改善数据库的负载压力. 应用服务器在写数据的时候,访问主数据库,主数据库通过主从复制机制将数据

EF6.0新特性-DbCommandInterceptor实现非SQL端读写分离

前几天看了一个基于sqlserver的负载均衡与读写分离的软件Moebius,实现的方式还是不错的,这使得用sqlserver数据库的同学时有机会对数据库进行更有效的优化了 看着人有做的东西,自己也想用EF来实现一个读写分离,所以就有了本篇文章,仓储大叔读写分离的思路是: 1 用sqlserver自带的发布.订阅实现主,从数据库的结构,同步这事由sql帮我们完成 2 配置文件建立几个供只读的数据库连接串 3 建立SQL命令拦截器 4 修改大叔的DbContextRepository基数,添加拦截

servlet/filter/listener/interceptor过滤器、监听器、拦截器区分

因为之前一直分不清过滤器和拦截器的区别,所以有了两者差不多的错觉,因此在这里总结下servlet/filter/listener/interceptor过滤器.监听器.拦截器. 在此之前先简单回顾下servlet: 概述:servlet是一种运行服务器端的java应用程序,它工作在客户端请求与服务器响应的中间层. 主要作用:在于交互式地浏览和修改数据,生成动态 Web 内容. 访问流程: 1,客户端发送请求至服务器端: 2,服务器将请求信息发送至 Servlet: 3,Servlet 生成响应内

【SSH2(实践篇)】--Struts2拦截器精解

上篇博客对Struts2的体系结构做了初步的了解,Struts2是以WebWork作为处理核心,并采用拦截器的机制来处理用户的请求,同时它还集成了Struts1丰富的标签库.另外上篇博客还对Struts2的配置使用进行了初步的介绍,下面将会集中讨论Struts2的拦截器. 一.拦截器 1.拦截器小介 拦截器的功能类似于web.xml文件中的Filter,能对用户的请求进行拦截,通过拦截用户的请求来实现对页面的控制.拦截器是在Struts-core-2.2.3.jar中进行配置的,原始的拦截器是在

struts2内置拦截器和自定义拦截器详解(附源码)

一.Struts2内置拦截器 Struts2中内置类许多的拦截器,它们提供了许多Struts2的核心功能和可选的高级特 性.这些内置的拦截器在struts-default.xml中配置.只有配置了拦截器,拦截器才可以正常的工作和运行.Struts 2已经为您提供丰富多样的,功能齐全的拦截器实现.大家可以至struts2的jar包内的struts-default.xml查看关于默认的拦截器与 拦截器链的配置.内置拦截器虽然在struts2中都定义了,但是并不是都起作用的.因为并不是所有拦截器都被加

【WebService】CXF拦截器的设置以及自定义CXF拦截器

WebService系列文章: [WebService]带你走进webservice的世界 [WebService]自定义WebService服务及其调用 [WebService]wsdl配置详解以及使用注解修改wsdl配置 [WebService]CXF处理javaBean等复合类型以及Map等复杂类型的数据 CXF的拦截器和以前学过的servlet的拦截器类似的,都是在开始或结束切入一段代码,执行一些逻辑之类的.我们可以在调用ws服务前设置拦截器,也可以在调用ws服务后设置拦截器,当然了,拦