前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >多库操作2:终于实现多个数据库操作

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

作者头像
老张的哲学
发布2022-04-11 11:28:30
2.2K0
发布2022-04-11 11:28:30
举报
文章被收录于专栏:NetCore 从壹开始

在上周的文章【多库操作:多个数据库的动态切换(一)】中,我们简单说了说,如何切换数据库,虽然实现了大部分的功能,但是最后也遗留了小问题,后来我和别的小伙伴讨论了下,那个小问题其实不是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注入方式,要把多个数据库连接同时注入进去:

代码语言:javascript
复制
 /// <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的唯一性:

代码语言:javascript
复制
 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接口:

代码语言:javascript
复制
 private SqlSugarClient _dbBase;
 
 public BaseRepository(IUnitOfWork unitOfWork)
 {
     _unitOfWork = unitOfWork;
     _dbBase = unitOfWork.GetDbClient();
 }

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

代码语言:javascript
复制
 private ISqlSugarClient _db
 {
     get
     {
         /* 如果要开启多库支持,
          * 1、在appsettings.json 中开启MutiDBEnabled节点为true,必填
          * 2、设置一个主连接的数据库ID,MainDB,必填
          */
         if (Appsettings.app(new string[] { "MutiDBEnabled" }).ObjToBool())
         {
             // 默认会获取当前Model的特性,看看是否配置了连接db的ConnID
             if (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:

代码语言:javascript
复制
  "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,这里我们用密码表做测试:

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

    
}

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

我们看看效果吧。

6

实现操作两个数据库效果

首先,开启多库配置:

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

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

代码语言:javascript
复制
 /// <summary>
 /// 测试多库连接
 /// </summary>
 /// <returns></returns>
 [HttpGet("TestMutiDBAPI")]
 [AllowAnonymous]
 public async Task<object> TestMutiDBAPI()
 {
     // 从主库(Sqlite)中,操作blogs
     var blogs = await _blogArticleServices.Query(d => d.bID == 1);

     // 从从库(Sqlserver)中,获取pwds
     var pwds = await _passwordLibServices.Query(d => d.PLID > 0;

     return new
     {
         blogs,
         pwds
     };
 }

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

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

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

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

7

检验事务操作是否正常

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

代码语言:javascript
复制
 [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;
 }

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

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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-12-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 NetCore 从壹开始 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
数据库
云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档