一、前言
感觉很久没写文章了,最近也比较忙,写的相对比较少,抽空分享基于Dapper
的分库分表开源框架core-data
的强大功能,更好的提高开发过程中的效率;在数据库的数据日积月累的积累下,业务数据库中的单表数据想必也越来越大,大到百万、千万、甚至上亿级别的数据,这个时候就很有必要进行数据库读写分离、以及单表分多表进行存储,提高性能,但是呢很多人不知道怎么去分库分表,也没有现成的分库分表的成熟框架,故不知道怎么下手,又怕影响到业务;现在我给大家推荐core-data
的分库分表开源框架。框架开源地址:https://github.com/overtly/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) =>{//MySqlreturn "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 Membersstatic 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;elseconnName = Secondary;return connName;}
}
代码中根据isMaster 参数来进行读写数据库链接参数的获取,以达到读写分离的功能,同时还支持前缀的配置支持,也开源自由配置多个数据库进行读取,只需要构造函数中获取配置即可。上面的分表Demo 单元测试运行后的结果例子如下图:已经按照MerchantId 字段进行分表
三、总结
到这里用户表已经根据商户ID进行分表存储了,这样就做到了读写分离及自定义分表策略存储数据,core-data
开源框架还支持更多的强大功能,实现了一系列的基础CRUD的方法,不用写任何的sql语句,Where表达式的支持,同时可以自定义复杂的sql语句,更多请访问框架开源地址:https://github.com/overtly/core-data. 完整的Demo 代码 已经放在github上,Demo代码结构图如下:地址:https://github.com/a312586670/NetCoreDemo
原创不易,觉得对你有帮助请给一个赞
往期精彩回顾
【.net core】电商平台升级之微服务架构应用实战
.Net Core微服务架构技术栈的那些事
Asp.Net Core 中IdentityServer4 授权中心之应用实战
Asp.Net Core 中IdentityServer4 授权中心之自定义授权模式
Asp.Net Core 中IdentityServer4 授权流程及刷新Token
Asp.Net Core 中IdentityServer4 实战之 Claim详解
Asp.Net Core 中IdentityServer4 实战之角色授权详解
Asp.Net Core 中间件应用实战中你不知道的那些事
Asp.Net Core Filter 深入浅出的那些事-AOP
Asp.Net Core EndPoint 终结点路由工作原理解读
ASP.NET CORE 内置的IOC解读及使用
♥ 给个[在看],是对我最大的支持 ♥