当前位置: 移动技术网 > IT编程>开发语言>.net > .net core 基于Dapper 的分库分表开源框架(core-data)

.net core 基于Dapper 的分库分表开源框架(core-data)

2020年05月04日  | 移动技术网IT编程  | 我要评论

电影魔方序列号,仙缘 梦如刃,妈祖庙在哪

一、前言

感觉很久没写文章了,最近也比较忙,写的相对比较少,抽空分享基于dapper 的分库分表开源框架core-data的强大功能,更好的提高开发过程中的效率;
在数据库的数据日积月累的积累下,业务数据库中的单表数据想必也越来越大,大到百万、千万、甚至上亿级别的数据,这个时候就很有必要进行数据库读写分离、以及单表分多表进行存储,提高性能,但是呢很多人不知道怎么去分库分表,也没有现成的分库分表的成熟框架,故不知道怎么下手,又怕影响到业务;现在我给大家推荐core-data的分库分表开源框架。框架开源地址:

二、基础

2.1 回顾

这里先来回顾下我上一篇文章中的技术栈路线图,如下:

今天从这张技术栈图中来详细分享一切的基础数据库底层操作orm。

2.2 core-data主要优势:

上一篇文章.net 微服务架构技术栈的那些事 中简单的介绍了core-data主要优势,如下:

  • 官方建议使用ddd 领域驱动设计思想开发
  • 支持多种数据库(mysql / sqlserver / sqlite ),简单配置添加链接的配置即可
  • 支持分表操作,自定义分表策略的支持
  • 支持表达式方式编写,减少写sql语句机械性工作
  • 可对dapper 进行扩展
  • 性能依赖于dapper 本身的性能,dapper 本身是轻量级orm ,官方测试性能都强于其他的orm
  • 框架支持framework4.6 - netstandard 2.0

三、实战详解

这里都仅仅分享核心的内容代码,不把整个代码贴出来,有需要完整demo源代码请访问 https://github.com/a312586670/netcoredemo
在我的解决方案的项目中 引用overt.core.data nuget包,如下图:

3.1 单表模式

创建用户实体代码如下:

    /// <summary>
    /// 标注数据库对应的表名
    /// </summary>
    [table("user")]
    public class userentity
    {
        /// <summary>
        /// 主键id,标注自增id
        /// </summary>
        [key, databasegenerated(databasegeneratedoption.identity)]
        public int userid { get; set; }

        /// <summary>
        /// 商户id
        /// </summary>
        public int merchantid { set; get; }

        /// <summary>
        /// 用户名
        /// </summary>
        public string username { get; set; }

        /// <summary>
        /// 真实姓名
        /// </summary>
        public string realname { get; set; }

        /// <summary>
        /// 密码
        /// </summary>
        public string password { get; set; }

        /// <summary>
        /// 添加时间
        /// </summary>
        public datetime addtime { get; set; }
    }

代码中通过[table("user")] 来和数据库表进行映射关联;
通过[key, databasegenerated(databasegeneratedoption.identity)] 标注自增主键.

3.2 默认分表策略

从单表模式改成分表模式,并且按照商户的模式进行分表,代码实体代码改造如下:

   /// <summary>
    /// 标注数据库对于的表名
    /// </summary>
    [table("user")]
    public class userentity
    {
        /// <summary>
        /// 主键id,标注自增id
        /// </summary>
        [key, databasegenerated(databasegeneratedoption.identity)]
        public int userid { get; set; }

        /// <summary>
        /// 商户id
        /// </summary>
        [submeter(bit =2)]
        public int merchantid { set; get; }

        /// <summary>
        /// 用户名
        /// </summary>
        public string username { get; set; }

        /// <summary>
        /// 真实姓名
        /// </summary>
        public string realname { get; set; }

        /// <summary>
        /// 密码
        /// </summary>
        public string password { get; set; }

        /// <summary>
        /// 添加时间
        /// </summary>
        public datetime addtime { get; set; }
    }

代码中 merchantid 字段上添加了[submeter(bit =2)]标注,并且指定了bit=2,将会分成根据merchantid字段取二进制进行md5 hash 取前两位分成256张表

分表模式源码分析

分表模式可以通过在字段上标注submeter属性,我们先来看看框架对于这个标注的源代码,源代码如下:

    /// <summary>
    /// 分表标识
    /// </summary>
    public class submeterattribute : attribute
    {
        /// <summary>
        /// 16进制位数
        /// 1 16
        /// 2 256
        /// 3 4096 
        /// ...
        /// </summary>
        public int bit { get; set; }
    }

开源框架中其中一个获得分表的表名称的扩展方法,仅仅只贴了一个扩展方法,有兴趣的可以下载开源框架进行源码阅读。

        /// <summary>
        /// 获取表名
        /// </summary>
        /// <param name="entity">实体实例</param>
        /// <param name="tablenamefunc"></param>
        /// <returns></returns>
        public static string gettablename<tentity>(this tentity entity, func<string> tablenamefunc = null) where tentity : class, new()
        {
            if (tablenamefunc != null)
                return tablenamefunc.invoke();

            var t = typeof(tentity);
            var mtablename = t.getmaintablename();
            var propertyinfo = t.getproperty<submeterattribute>();
            if (propertyinfo == null) // 代表没有分表特性
                return mtablename;

            // 获取分表
            var suffix = propertyinfo.getsuffix(entity);
            return $"{mtablename}_{suffix}";
        }

        /// <summary>
        /// 获取后缀(默认根据submeterattribute 标注的位数进行md5 hash 进行分表)
        /// </summary>
        /// <param name="val"></param>
        /// <param name="bit"></param>
        /// <returns></returns>
        internal static string getsuffix(string val, int bit = 2)
        {
            if (string.isnullorempty(val))
                throw new argumentnullexception($"分表数据为空");
            if (bit <= 0)
                throw new argumentoutofrangeexception("length", "length必须是大于零的值。");

            var result = encoding.default.getbytes(val.tostring());    //tbpass为输入密码的文本框
            var md5provider = new md5cryptoserviceprovider();
            var output = md5provider.computehash(result);
            var hash = bitconverter.tostring(output).replace("-", "");  //tbmd5pass为输出加密文本

            var suffix = hash.substring(0, bit).toupper();
            return suffix;
        }

源代码中通过submeterattribute 特性进行对字段进行标注分表,可以传对应的bit参数进行框架默认的分表策略进行分表,但是很多情况下我们需要自定义分表策略,那我们应该怎么去自定义分表策略呢?我们先等一下来实践自定义分表策略,先来创建用户的repository,代码如下
iuserrepository:

public interface iuserrepository : ibaserepository<userentity>
{
}

需要继承ibaserepository<t>的接口,该接口默认实现了基本的方法,开源框架中ibaserepository<t>代码方法如下图:

创建完iuserrepository接口后,我们来创建它的实现userrepository,代码如下:

public class userrepository : baserepository<userentity>, iuserrepository
{
        public userrepository(iconfiguration configuration)
            : base(configuration, "user")
        {
        }
 }

从代码中userrepository类继承了baserepository<t>类,我们来看看这个abstract类的基本结构,如下图:

开源框架中baserepository<t>抽象类继承了propertyassist类,我们再来看看它的有哪些方法,如下图:

从图中可以看到定义了一系列的virtual方法,那既然是virtual方法我们就可以进行重写

  • createscriptfunc:自动创建脚本数据表方法
  • tablenamefunc:可以进行自定义分表策略

3.3 自定义分表策略

我们来实现上面提出的自定义分表策略问题(根据商户id来进行分表,并且自动把不存在的表进行初始化创建),代码改造如下:
iuserrepository:

public interface iuserrepository : ibaserepository<userentity>
{
    int merchantid { set; get; }
}

userrepository 代码改造如下:

public class userrepository : baserepository<userentity>, iuserrepository
{
        public userrepository(iconfiguration configuration)
            : base(configuration, "user")
        {
        }

        /// <summary>
        /// 用于根据商户id来进行分表
        /// </summary>
        public int merchantid { set; get; }

        //自定义分表策略
        public override func<string> tablenamefunc => () =>
        {
            var tablename = $"{getmaintablename()}_{merchantid}";
            return tablename;
        };

        //自动创建分表的脚本
        public override func<string, string> createscriptfunc => (tablename) =>
        {
            //mysql
            return "create table `" + tablename + "` (" +
                   "  `userid` int(11) not null auto_increment," +
                   "  `username` varchar(200) default null," +
                   "  `password` varchar(200) default null," +
                   "  `realname` varchar(200) default null," +
                   "  `addtime` datetime default null," +
                   "  `merchantid` int(11) not null," +
                   "  primary key(`userid`)" +
                   ") engine = innodb auto_increment = 1 default charset = utf8mb4; ";
        };
    }

3.4 数据库读写分离

我们再来看看开源框架的基类代码结构截图,如下:

对于查询的基本常用的方法都有一个ismaster=false的参数,该参数就是用于是否读取主库,用于基本的主从数据库的分离,也就是读写分离,那改怎么配置读写分离数据库呢
链接字符串如下图:

分别指定了主从数据库的链接字符串.
我们来分析源代码,核心框架源代码如下:

/// <summary>
/// 连接配置信息获取
/// 1. master / secondary
/// 2. xx.master / xx.secondary
/// </summary>
public class datasettings
{
        #region static private members
        static readonly string _connnmeofmaster = "master";
        static readonly string _connnameofsecondary = "secondary";
        static readonly string _connnameofpoint = ".";
        static string _connnameofprefix = "";
        #endregion

        #region private member
        /// <summary>
        /// 主库
        /// </summary>
        private string master
        {
            get { return $"{_connnameofprefix}{_connnmeofmaster}"; }
        }
        /// <summary>
        /// 从库
        /// </summary>
        private string secondary
        {
            get
            {
                return $"{_connnameofprefix}{_connnameofsecondary}";
            }
        }
        #endregion

        /// <summary>
        /// 获取链接名称
        /// </summary>
        /// <param name="ismaster"></param>
        /// <param name="store">不能包含点</param>
        /// <returns></returns>
        private string key(bool ismaster = false, string store = "")
        {
            _connnameofprefix = string.isnullorempty(store) ? "" : $"{store}{_connnameofpoint}";
            var connname = string.empty;
            if (ismaster)
                connname = master;
            else
                connname = secondary;

            return connname;
        }
}

代码中根据ismaster 参数来进行读写数据库链接参数的获取,以达到读写分离的功能,同时还支持前缀的配置支持,也开源自由配置多个数据库进行读取,只需要构造函数中获取配置即可。
上面的分表demo 单元测试运行后的结果例子如下图:

已经按照merchantid 字段进行分表

三、总结

到这里用户表已经根据商户id进行分表存储了,这样就做到了读写分离及自定义分表策略存储数据,core-data 开源框架还支持更多的强大功能,实现了一系列的基础crud的方法,不用写任何的sql语句,where表达式的支持,同时可以自定义复杂的sql语句,更多请访问框架开源地址:.
完整的demo 代码 已经放在github上,demo代码结构图如下:

地址:https://github.com/a312586670/netcoredemo

原创不易,觉得对你有帮助请给一个赞

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网