多库操作2:终于实现多个数据库操作

更多精彩推荐,上午10点到达

圣诞节快乐

在上周的文章【多库操作:多个数据库的动态切换(一)】中,我们简单说了说,如何切换数据库,虽然实现了大部分的功能,但是最后也遗留了小问题,后来我和别的小伙伴讨论了下,那个小问题其实不是Bug,而是设计思路的偏差,所以我又重新思考了一下,做了一定的优化,从而实现了多个数据库共存的情况,当然目前这个也能满足事务提交。

那下面,我们就重新再来说说,【多库】操作到底是怎样的?具体的操作,可以点击【阅读原文】我写的wiki(https://github.com/anjoy8/Blog.Core/wiki/support-muti-db)。

借鉴大佬思路:@銀翼の奇術師

1

常见的两种多库操作方式  

之前咱们简单说过,不过这里再详细的说一说,多库操作到底是如何操作的。

在平时开发中,我们习惯了面向数据库开发,就是一个项目下来以后,我们就会赶紧的去构思逻辑,然后领导一声令下,五点办公室开会,大家一顿操作,设计了一个堪称完美的数据库表结构,无论是备用字段,还是以后的业务逻辑的扩展,都应有尽有。后来为了满足可能遇到的各种情况,表是能详细就详细,不怕多,就怕改,这也是为何现在ORM很火的原因,当年我也是改了很久的DBHelper,修改一下,整个人都崩溃????

后来随着业务的发展,和数据库的瓶颈,就出现了分库的口号,大家开始拆分数据库了,常见的有两种模式:

①、读写分离,多个数据库的表结构是一样的,但是Query和Command不是在一起的,这样能突破瓶颈,使得业务能进一步提高。

②、模块分离,还是多个数据库,只不过每个数据库负责不同的模块,比如密码库,就只有密码表相关的,用户库仅仅是用户相关的,商品库就是商品相关的。

当然如果使用了微服务,是完全没有这个问题的,微服务就是多个api服务,每个api一个数据库,一个模块,一个业务逻辑的,比如这样:

(图片来源网络,侵删)

第一种方案呢,我在我的第二个DDD系列已经说到了,这里应该就不会在Blog.Core里再添加这个功能了,那今天咱们就做一下第②个方案,多个数据库负责不同的模块,可以进行不同的切换,当然如果以后想要新增其他模块功能,只需要自己新建个数据库就行了,然后配置连接字符串,轻松搞定,具体的好处很多,用到的时候肯定都知道了。

下面,我们就详细来说说具体的操作过程吧,随便交代一下,我用的ORM是Sqlsugar5.0.10+版本,其他ORM我没有尝试,自行修改。

2

修改Sqlsugar服务注入方式  

在SqlsugarSetup.cs中,修改SqlSugarClient注入方式,要把多个数据库连接同时注入进去:

 /// <summary>/// SqlSugar 启动服务/// </summary>public static class SqlsugarSetup{public static void AddSqlsugarSetup(this IServiceCollection services){if (services == null) throw new ArgumentNullException(nameof(services));// 默认添加主数据库连接MainDb.CurrentDbConnId = Appsettings.app(new string[] { "MainDB" });// 把多个连接对象注入服务,这里必须采用Scope,因为有事务操作services.AddScoped<ISqlSugarClient>(o =>{var listConfig = new List<ConnectionConfig>()BaseDBConfig.MutiConnectionString.ForEach(m =>{listConfig.Add(new ConnectionConfig(){ConfigId = m.ConnId.ObjToString().ToLower(),ConnectionString = m.Conn,DbType = (DbType)m.DbType,IsAutoCloseConnection = true,IsShardSameThread = false,MoreSettings = new ConnMoreSettings(){IsAutoRemoveDataCache = true}//InitKeyType = InitKeyType.SystemTable});});return new SqlSugarClient(listConfig);});}}

3

修改工作单元,获取DB服务  

在UnitOfWork.cs文件中,把刚刚第二步中注册的服务,通过构造函数注入获取,并且做相应的事务操作,注意,这里必须要保证同一个scope,如果想要实现事务,就必须保证DB的唯一性:

 public class UnitOfWork : IUnitOfWork{private readonly ISqlSugarClient _sqlSugarClient;public UnitOfWork(ISqlSugarClient sqlSugarClient){_sqlSugarClient = sqlSugarClient;}/// <summary>/// 获取DB,保证唯一性/// </summary>/// <returns></returns>public SqlSugarClient GetDbClient(){// 必须要as,后边会用到切换数据库操作return _sqlSugarClient as SqlSugarClient;}public void BeginTran(){GetDbClient().BeginTran();}public void CommitTran(){try{GetDbClient().CommitTran(); //}catch (Exception ex){GetDbClient().RollbackTran();throw ex;}}public void RollbackTran(){GetDbClient().RollbackTran();}}

如果修改好了,工作单元,记得也要修改要工作单元接口。

4

动态获取 _db 实例 

在上边,我们在工作单元uow(unitOfWork)中,注入了数据库实例,那现在就要获取这个实例了,很简单,直接基类仓储BaseRepository.cs构造函数中,依赖注入我们的IUnitOfWork接口:

 private SqlSugarClient _dbBase;public BaseRepository(IUnitOfWork unitOfWork){_unitOfWork = unitOfWork;_dbBase = unitOfWork.GetDbClient();}

获取到这个 _dbBase 以后,其实这个时候已经可以了,我们就可以任意的使用这个db实例,但是我们今天的目的是要动态切换,重头戏来了,既然我们每次请求都需要这个db,那简单,我们就修改它的属性:

 private ISqlSugarClient _db{get{/* 如果要开启多库支持,* 1、在appsettings.json 中开启MutiDBEnabled节点为true,必填* 2、设置一个主连接的数据库ID,MainDB,必填*/if (Appsettings.app(new string[] { "MutiDBEnabled" }).ObjToBool()){// 默认会获取当前Model的特性,看看是否配置了连接db的ConnIDif (typeof(TEntity).GetTypeInfo().GetCustomAttributes(typeof(SugarTable), true).FirstOrDefault((x => x.GetType() == typeof(SugarTable))) is SugarTable sugarTable){_dbBase.ChangeDatabase(sugarTable.TableDescription.ToLower());}else{// 如果不存在,则表明当前Model是主数据库操作// 这个配置的地点,看文章上边第二节,注入服务的时候_dbBase.ChangeDatabase(MainDb.CurrentDbConnId.ToLower());}}return _dbBase;}}

5

实体类和连接字符串的配置  

首先呢,我们需要在appsettings.json中,配置多个库的连接字符串,注意,如果想要打开多个,就要把那几个的Enabled全部设置为true:

  "MainDB": "WMBLOG_SQLITE",// 当前主库的连接字符串,不填写的话,默认是下边true的第一个"MutiDBEnabled": false,// 是否开启多库,默认是false"DBS": [/*MySql = 0,SqlServer = 1,Sqlite = 2,Oracle = 3,PostgreSQL = 4*/{"ConnId": "WMBLOG_SQLITE","DBType": 2,"Enabled": true,"Connection": "WMBlog.db" //只写数据库名就行,我会拼接字符串},{"ConnId": "WMBLOG_MSSQL","DBType": 1,"Enabled": true,"Connection": "Server=.;Database=WMBlogDB;User ID=sa;Password=123;","ProviderName": "System.Data.SqlClient"},{"ConnId": "WMBLOG_MYSQL","DBType": 0,"Enabled": false,"Connection": "Server=localhost; Port=3306;Stmt=; Database=wmblogdb; Uid=root; Pwd=456;"},{"ConnId": "WMBLOG_ORACLE","DBType": 3,"Enabled": false,"Connection": "Provider=OraOLEDB.Oracle; Data Source=WMBlogDB; User Id=sss; Password=789;","OracleConnection_other1": "User ID=sss;Password=789;Data Source=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.8.65)(PORT=1521)))(CONNECT_DATA=(SERVICE_NAME = orcl)))"}],

然后我们修改一下Model,配置SugarTable特性,第一个参数是表明,第二个是对应的db连接字符串的ConnID,这里我们用密码表做测试:

/// <summary>
/// 密码库表
/// </summary>
[SugarTable("PasswordLib", "WMBLOG_MSSQL")]
public class PasswordLib
{[SugarColumn(IsNullable = false, IsPrimaryKey = true, IsIdentity = true)]public int PLID { get; set; }}

到了这里,我们就可以随意的做多库操作了。

我们看看效果吧。

6

实现操作两个数据库效果

首先,开启多库配置:

我们测试两个数据库,一个是Sqlite主库,一个是Sqlserver从库,

从主库中,获取博客信息,从库中获取密码表信息,就是刚刚我们在上边配置的实体。

 /// <summary>/// 测试多库连接/// </summary>/// <returns></returns>[HttpGet("TestMutiDBAPI")][AllowAnonymous]public async Task<object> TestMutiDBAPI(){// 从主库(Sqlite)中,操作blogsvar blogs = await _blogArticleServices.Query(d => d.bID == 1);// 从从库(Sqlserver)中,获取pwdsvar pwds = await _passwordLibServices.Query(d => d.PLID > 0;return new{blogs,pwds};}

为了做对比效果,我把这两个表,从数据库中删掉,也就是blog在从库中删掉,pwd在主库中删掉:

我们做一个动图,来看看效果:

 

尽管两个表在对方的数据库不存在,但是我们还是获取到了数据:


那是不是这样就高枕无忧了呢,别慌!咱们是事务不会被破坏吧!来再试试。

7

检验事务操作是否正常  

项目中有一个测试接口,大概意思就是先读取一个表数据,然后添加一条数据,中间制造一个异常,最后做回滚操作,看看是否添加的数据被删掉。

 [HttpGet]public async Task<IEnumerable<string>> Get(){List<string> returnMsg = new List<string>() { };try{returnMsg.Add($"Begin Transaction");_unitOfWork.BeginTran();var passwords = await _passwordLibServices.Query(d=>d.IsDeleted==false);returnMsg.Add($"first time : the count of passwords is :{passwords.Count}");returnMsg.Add($"insert a data into the table PasswordLib now.");var insertPassword = await _passwordLibServices.Add(new PasswordLib(){IsDeleted = false,plAccountName = "aaa",plCreateTime = DateTime.Now});passwords = await _passwordLibServices.Query(d => d.IsDeleted == false);returnMsg.Add($"second time : the count of passwords is :{passwords.Count}");returnMsg.Add($" ");//......var guestbooks = await _guestbookServices.Query();returnMsg.Add($"first time : the count of guestbooks is :{guestbooks.Count}");int ex = 0;returnMsg.Add($"There's an exception!!");returnMsg.Add($" ");int throwEx = 1 / ex;var insertGuestbook = await _guestbookServices.Add(new Guestbook(){username = "bbb",blogId = 1,createdate = DateTime.Now,isshow = true});guestbooks = await _guestbookServices.Query();returnMsg.Add($"first time : the count of guestbooks is :{guestbooks.Count}");returnMsg.Add($" ");_unitOfWork.CommitTran();}catch (Exception){_unitOfWork.RollbackTran();var passwords = await _passwordLibServices.Query();returnMsg.Add($"third time : the count of passwords is :{passwords.Count}");var guestbooks = await _guestbookServices.Query();returnMsg.Add($"third time : the count of guestbooks is :{guestbooks.Count}");}return returnMsg;}

动图太大,这么不放了,肯定是正确的,直接看结果吧:

打完收工!距离微服务又近了一步。

"

每一个努力的,或者正在努力的人,都应该值得被尊重。


                        ——老张的哲学

"

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/312555.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据结构作业1

1.给定NNN的三维数组A&#xff0c;则在不改变数组的前提下&#xff0c;查找最小元素的时间复杂度是&#xff1a;D A.O(N​2​​) B.O(NlogN) C.O(N​3​​logN) D.O(N​3​​) 2.For the following piece of code for(i0; i<n; i)for(ji; j>0; j/2)printf(“%d\n”, j);…

服务发现技术是如何演进出来的?

昨天写了一篇<微服务的时间和成本去哪儿了>&#xff0c;有人在底下留言&#xff1a;我的回答是&#xff1a;"微服务可以不用服务发现和负载均衡吗&#xff1f;它是微服务一个核心组件。怎么能说没有关系&#xff1f;"我觉得有必要来思考和总结一下服务发现技术…

7-5 两个有序链表序列的合并 (20 分)

已知两个非降序链表序列S1与S2&#xff0c;设计函数构造出S1与S2合并后的新的非降序链表S3。 输入格式: 输入分两行&#xff0c;分别在每行给出由若干个正整数构成的非降序序列&#xff0c;用−1表示序列的结尾&#xff08;−1不属于这个序列&#xff09;。数字用空格间隔。 …

3分钟搞懂前后端开发的区别

上周末见了好多开发的年轻朋友&#xff0c;问了我一个问题&#xff1a;“前后端的区别和要求是什么&#xff1f;”分不清前后端开发的区别和要求&#xff0c;一种是因为前后端都了解&#xff0c;号称“全栈工程师”&#xff0c;但又什么都不是很精通&#xff1b;另一种是前端的…

7-4 螺旋方阵 (20 分)

所谓“螺旋方阵”&#xff0c;是指对任意给定的N&#xff0c;将1到NN的数字从左上角第1个格子开始&#xff0c;按顺时针螺旋方向顺序填入NN的方阵里。本题要求构造这样的螺旋方阵。 输入格式&#xff1a; 输入在一行中给出一个正整数N&#xff08;<10&#xff09;。 输出格…

基于 Kubernetes 的微服务部署即代码

在基于 Kubernetes 的基础设施即代码一文中&#xff0c;我概要地介绍了基于 Kubernetes 的 .NET Core 微服务和 CI/CD 动手实践工作坊使用的基础设施是如何使用代码描述的&#xff0c;以及它的自动化执行过程。如果要查看基于 Kubernetes 的基础设施即代码架构全图&#xff0c;…

使用Arduino开发ESP32开发环境搭建

1.打开Arduino IDE&#xff0c;选择文件->首选项->设置 复制下方的ESP32板管理网址&#xff0c;添加到附加开发板管理器中&#xff1a; https://dl.espressif.com/dl/package_esp32_index.json 2.选择&#xff1a;工具->开发板->开发板管理器 在弹出的对话框中搜索…

.NET ORM FreeSql 第一个正式版本发布 v1.0.0

一、简介FreeSql 是 .NET 平台下的对象关系映射技术(O/RM)&#xff0c;支持 .NetCore 2.1 或 .NetFramework 4.0 或 Xamarin。从 0.0.1 发布到今历时整整一年的迭代更新&#xff0c;现在终于敢发布第一个正式版。本文内容从简&#xff0c;介绍项目的主要功能框架&#xff0c;以…

Windows上搭建EMQTT服务器

1.官网下载EMQ 2.复制如图文件路径 3.打开终端cmd&#xff0c;输入&#xff1a; 再输入&#xff1a; 4.打开浏览器&#xff0c;进入&#xff1a;http://192.168.1.25:18083/* (localhost可打开终端输入ipconfig查看) 用户名&#xff1a;admin 密码&#xff1a;public

.NET解所有相机RAW格式照片

再聊.NET解相机RAW格式照片上次我发了一篇文章《用.NET解索尼相机ARW格式照片》&#xff0c;提到通过安装 SonyRawFileDecoder的方式&#xff0c;然后调用 WindowsImagingComponents来解析 RAW格式文件。后来我经过进一步研究、探索&#xff0c;发现还有更简单的办法。新的方法…

6-3 查找子串 (20 分)

本题要求实现一个字符串查找的简单函数。 函数接口定义&#xff1a; char *search( char *s, char *t );函数search在字符串s中查找子串t&#xff0c;返回子串t在s中的首地址。若未找到&#xff0c;则返回NULL。 裁判测试程序样例&#xff1a; #include <stdio.h> #d…

6-4 建立学生信息链表 (20 分)

本题要求实现一个将输入的学生成绩组织成单向链表的简单函数。 函数接口定义&#xff1a; void input();该函数利用scanf从输入中获取学生的信息&#xff0c;并将其组织成单向链表。链表节点结构定义如下&#xff1a; struct stud_node {int num; /*学号*/…

AspNetCore结合Redis实践消息队列

这是年中首发在博客园上的文章&#xff0c;个人觉得是AspNetCore结合Redis做的一次比较优秀的消息队列重构&#xff0c;其中对于点对点/发布-订阅的思路应该也是面试必考题。引言.Net TPL Dataflow是一个进程内数据流管道&#xff0c;应对高并发、低延迟的要求非常有效&#xf…

数据结构:链表(c语言)

1.链表简介&#xff1a; 链表和数组一样&#xff0c;都是存储数据&#xff0c;链表是非连续&#xff0c;非顺序的存储结构 链表是灵活的内存动态管理&#xff08;随机分配空间&#xff09;&#xff0c;删除创建结点非常方便 链表组成&#xff1a;由一系列结点组成 链表结点&…

提升Azure App Service的几个建议

本文介绍了6个技巧&#xff0c;这些技巧可以改善Azure App Service托管应用程序的性能。其中一些技巧是你现在就可以进行的配置变更&#xff0c;而其他技巧则可能需要对应用程序进行一些重新设计和重构&#xff0c; 本文的几个技巧对于常规企业部署依旧有指引作用。长话短说开发…

单向链表的逆转(数据结构)(c语言)

逆转单向链表的意思是&#xff1a;给定你一个单向链表&#xff0c;一个整数n&#xff08;n为要逆转的结点数&#xff09;&#xff0c;要求你把链表从头结点到第n个结点给逆转过来 图示&#xff1a; 给出一个单向链表&#xff0c;一个整数n4。也就是要求把该链表从头结点&#x…

广东职业教育信息化研究会2019年会暨区块链专题研讨会

兹定于2019年12月28日&#xff08;星期六&#xff09;上午9:30召开广东职业教育信息化研究会2019年会暨专题研讨会&#xff0c;本次会议由广东职业教育信息化研究会主办&#xff0c;华南师范大学网络教育学院协办。会议地址&#xff1a;广州市天河区中山大道西55号华南师范大学…

Serial.println()和Serial.print() (Arduino编程)

Arduino的输出基本就用到两个函数&#xff1a;print和println 区别是后者比前者多了回车换行 Serial.println(data)从串行端口输出数据&#xff0c;跟随一个回车&#xff08;ASCII 13或’r’&#xff09;和一个换行符&#xff08;ASCII 10或’n’&#xff09;&#xff0c;这个…

如何快速融入一个团队?

作者&#xff1a;邹溪源&#xff0c;长沙资深互联网从业者&#xff0c;架构师社区特邀嘉宾&#xff01;一我们难免需要离开一个圈子&#xff0c;加入一个陌生的集体。毋庸置疑&#xff0c;离开熟知的圈子&#xff0c;走向未知的圈子难免会产生许多畏惧甚至情怯&#xff0c;这都…

使用Arduino开发ESP32:wifi基本功能使用

1.建立网络&#xff08;AP&#xff09; 2.连接网络&#xff08;STA&#xff09; 3.扫描网络 1.建立网络&#xff08;AP&#xff09; 只需两步&#xff1a;1.引用WiFi库include<WiFi.h> 2.启动AP网络WiFi.softAP(ssid) 将下面代码上传到模块中&#xff1a; #include &l…