昨天(星期五)下班,19:00左右回到家,洗个澡,然后20:30左右开始写代码,写完代码之后,上床看了《生活大爆炸10季》17、18两集,发现没有更新到19集,瞄了一眼手机,竟然已经是凌晨02:00多了,关掉电视睡觉,10:30左右被老婆电话吵醒,洗漱完毕,去麦当劳吃了一个早餐,然后屁颠屁颠地坐地铁到很远的地方去爬山。爬山回来之后,闲来无事,写篇文章记录一下昨晚所花的几个小时干的事情——使用EntityFrameworkCore实现Repository<TEntity>UnitOfWork<TContext>,支持MySQL分库分表。

由于是使用业余时间写来玩的,时间也有限,所以,全部代码做了一个基本假设:Repository<TEntity>UnitOfWork<TContext>只支持同一个IP上的MySQL分库分表,不同IP上的MySQL分库分表,需要使用不同的Repository<TEntity>UnitOfWork<TContext>对象。以下示例代码,假设数据库是按年分库按月分表。

EntityFrameworkCore默认并不支持分库分表,我们看一眼EntityFrameworkCore默认生成的SQL:

Executed DbCommand [Parameters=[@p2='?', @p4='?' (Size = 8000), @p6='?' (Size = 8000)], CommandType='Text', CommandTimeout='0']INSERT INTO `t_user_201703` (`Fis_deleted`, `Fpassword`, `Fname`)VALUES (@p2, @p4, @p6);SELECT LAST_INSERT_ID();

默认生成的SQL并没有带上库名,而想要让EntityFrameworkCore支持MySQL分库分表,首要条件是必须能做到可以动态地改变库名表名。软件界有一句老话叫:凡是做不到的就多抽象一层,所以,想要让EntityFrameworkCore支持MySQL分库分表,我抽象了以下两个接口, IRepository<TEntity>IUnitOfWork

很多人都自己动手实现过RepositoryUnitOfWork,虽然各自实现不尽相同,但是其实现本身并没有难度,但在这里,我们需要特别关注两个方法:void ChangeTable(string table)void ChangeDatabase(string database)

    /// <summary>    /// Changes the table name. This require the tables in the same database.    /// </summary>    /// <param name="table"></param>    /// <remarks>    /// This only been used for supporting multiple tables in the same model. This require the tables in the same database.    /// </remarks>      void ChangeTable(string table);/// <summary>    /// Changes the database name. This require the databases in the same machine.    /// </summary>    /// <param name="database">The database name.</param>    /// <remarks>    /// This only been used for supporting multiple databases in the same model. This require the databases in the same machine.    /// </remarks>      void ChangeDatabase(string database);

怎么实现这两个方法,就需要一定的技术功底了,我以前在一家创业公司的时候,因为看不惯架构师自以为是的样子,自己动手写了一个轻量级的ORM框架,如果以后有时间,我打算写一篇《如何基于Dapper实现一个轻量级的ORM框架》的文章。ORM框架背后的动机很单纯,就是数据库Domain之间的一种双向映射,真正把这种单纯的动机搞复杂是的那些性能优化,各种缓存实现。而从Domain到数据库这一单方向上的映射,在.NET领域借助了一种代码即数据的思想,再细化到C#语言代码即数据就是表达式树。所以,我们有理由相信:SQL是根据表达式树生成的。现在我们已经找准了方向,那么我们看看EntityFrameworkCore在什么地方生成表名的,也就是说,我们只需要修改一下生成表名的代码,就可以做到动态生成database.table SQL。EntityFrameworkCore是通过TableExpression来生成表名的:

public class TableExpression{public virtual string Table { get; }public virtual string Schema { get; }}

如果你MySQL知识至少跟我一样的水平的话,看到TableExpression表达式有一个Schema是不是立即就可以想到:哈哈,太好了,我压根就不用修改EntityFrameworkCore本身的代码就可以实现。为什么呢?好吧,看看MySQL官网怎么说Schema的:

In MySQL, physically, a schema is synonymous with a database. You can substitute the keyword SCHEMA instead of DATABASE in MySQL SQL syntax, for example using CREATE SCHEMA instead of CREATE DATABASE. Some other database products draw a distinction. For example, in the Oracle Database product, a schema represents only a part of a database: the tables and other objects owned by a single user.

好吧,Schema就是Database,那么我们就用Schema.Table来表示database.table。现在事情就变得简单了,变成了我们如何动态地改变SchemaTable了,以下是我提供的简化实现:

/// <summary> /// Changes the database name. This require the databases in the same machine. /// </summary> /// <param name="database">The database name.</param> /// <remarks> /// This only been used for supporting multiple databases in the same model. This require the databases in the same machine. /// </remarks>public void ChangeDatabase(string database){if (_context.Model.Relational() is RelationalModelAnnotations relational){relational.DatabaseName = database;}var connection = _context.Database.GetDbConnection();if (connection.State.HasFlag(ConnectionState.Open)){connection.ChangeDatabase(database);}var items = _context.Model.GetEntityTypes();foreach (var item in items){if (item.Relational() is RelationalEntityTypeAnnotations extensions){extensions.Schema = database;}}}/// <summary> /// Changes the table name. This require the tables in the same database. /// </summary> /// <param name="table"></param> /// <remarks> /// This only been used for supporting multiple tables in the same model. This require the tables in the same database. /// </remarks>public void ChangeTable(string table){if (_dbContext.Model.FindEntityType(typeof(TEntity)).Relational() is RelationalEntityTypeAnnotations relational){relational.TableName = table;}}

OK, 虽然有点low,但是毕竟支持了MySQL分库分表,看看怎么用:

namespace QuickStart.Controllers{[Route("api/[controller]")]public class UserController : ApiController{private readonly IUnitOfWork _unitOfWork;// 1. IRepositoryFactory used for readonly scenario;        // 2. IUnitOfWork used for read/write scenario;        // 3. IUnitOfWork<TContext> used for multiple databases scenario;public UserController(IUnitOfWork unitOfWork){_unitOfWork  = unitOfWork;unitOfWork.ChangeDatabase($"rigofunc_{DateTime.Now.Year}");var userRepo = unitOfWork.GetRepository<User>();var postRepo = unitOfWork.GetRepository<Post>();var ym = DateTime.Now.ToString("yyyyMM");userRepo.ChangeTable($"t_user_{ym}");postRepo.ChangeTable($"t_post_{ym}");var user = new User{//UserId = 123,                UserName = "rigofunc",Password = "password"};userRepo.Insert(user);var post = new Post{//PostId = 123,                UserId = user.UserId,Content = "What a piece of junk!"};postRepo.Insert(post);unitOfWork.SaveChanges();var find = userRepo.Find(user.UserId);find.Password = "p@ssword";unitOfWork.SaveChanges();}[HttpGet]public IPagedList<User> Get(){_unitOfWork.ChangeDatabase($"rigofunc_2018");var userRepo = _unitOfWork.GetRepository<User>();return userRepo.Query(u => true).OrderBy(u => u.UserId).ToPagedList(0, 20);}}}

以下是生成的SQL:

      Executed DbCommand [Parameters=[@p2='?', @p4='?' (Size = 8000), @p6='?' (Size = 8000)], CommandType='Text', CommandTimeout='0']INSERT INTO `rigofunc_2017`.`t_user_201703` (`Fis_deleted`, `Fpassword`, `Fname`)VALUES (@p2, @p4, @p6);SELECT LAST_INSERT_ID();Executed DbCommand [Parameters=[@p10='?' (Size = 8000), @p12='?', @p14='?'], CommandType='Text', CommandTimeout='0']INSERT INTO `rigofunc_2017`.`t_post_201703` (`Fcontent`, `Fis_deleted`, `Fuser_id`)VALUES (@p10, @p12, @p14);SELECT LAST_INSERT_ID();Executed DbCommand [Parameters=[@p0='?', @p3='?', @p4='?' (Size = 8000)], CommandType='Text', CommandTimeout='0']UPDATE `rigofunc_2017`.`t_user_201703` SET `Fpassword` = @p4WHERE `Fid` = @p0 AND `Fis_deleted` = @p3;SELECT ROW_COUNT();Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='0']SELECT `u`.`Fid`, `u`.`Fis_deleted`, `u`.`Fpassword`, `u`.`Fname`FROM `rigofunc_2017`.`t_user_201703` AS `u`ORDER BY `u`.`Fid`Executed DbCommand (2ms) [Parameters=[], CommandType='Text', CommandTimeout='0']SELECT `u`.`Fid`, `u`.`Fis_deleted`, `u`.`Fpassword`, `u`.`Fname`FROM `rigofunc_2018`.`t_user_201703` AS `u`ORDER BY `u`.`Fid`

ABOUT ME:

I’m a software architect, particularly love .NET Core, but I also embrace all the new stuff. I’m on GitHub with xyting, and my packages publish on NuGet with rigofunc

原文地址:http://www.xyting.org/2017/03/25/EFCore-Support-MySQL-Multiple-Databases-And-Tables-sharding.html


.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

使用EntityFrameworkCore实现Repository, UnitOfWork,支持MySQL分库分表相关推荐

  1. 高可用Mysql架构_Mysql主从复制、Mysql双主热备、Mysql双主双从、Mysql读写分离(Mycat中间件)、Mysql分库分表架构(Mycat中间件)的演变...

    [Mysql主从复制] 解决的问题 数据分布:比如一共150台机器,分别往电信.网通.移动各放50台,这样无论在哪个网络访问都很快.其次按照地域,比如国内国外,北方南方,这样地域性访问解决了. 负载均 ...

  2. 如何彻底解决烦人的 MySQL 分库分表问题?写一个更好的数据库!

    作者 | 黄东旭 责编 | 郭   芮 我还清楚记得,五年前的这个时候,当时还在豌豆荚,午后与刘奇和崔秋的闲聊关于未来数据库的想象,就像一粒种子一样,到了今天看起来也竟枝繁叶茂郁郁葱葱,有点感慨.按照 ...

  3. MySQL运维(二)MySQL分库分表概念及实战、读取分离详解

    MySQL运维(二)MySQL分库分表详解.读取分离详解 1.MySQL分库分表相关概念 1.1 分库分表概念 1.1.1 分库的原因 分库:就是一个数据库分成多个数据库,部署到不同机器. 如果业务量 ...

  4. MySQL分库分表原理

    分库分表原理 前言 垂直拆分 水平拆分 分库分表后引入的问题 分库分表中间件 前言 在互联网还未崛起的时代,我们的传统应用都有这样一个特点:访问量.数据量都比较小,单库单表都完全可以支撑整个业务.随着 ...

  5. 【分布式mysql分库分表中间件sharding】

    分布式mysql分库分表中间件,sharding领域的一站式解决方案.具备丰富.灵活的路由算法支持,能够方便DBA实现库的水平扩容和降低数据迁移成本.shark采用应用集成架构,放弃通用性,只为换取更 ...

  6. MySQL分库分表会带来哪些问题?分库分表问题

    MySQL分库分表会带来哪些问题? 分库分表能有效的环节单机和单库带来的性能瓶颈和压力,突破网络IO.硬件资源.连接数的瓶颈,同时也带来了一些问题.下面将描述这些技术挑战以及对应的解决思路. 分库分表 ...

  7. MySQL分库分表和优化

    第九阶段模块三 分库分表技术之MyCat 1.海量存储问题 1.1 背景描述 随着互联网的发展,数据的量级也是成指数的增长,从GB到TB到PB.对数据的各种操作也是愈加的困难,传统的关系性数据库已经无 ...

  8. 利用RadonDB实现MySQL分库分表

    利用RadonDB实现MySQL分库分表 RadonDB是青云上提供的MySQL分布式解决方案,提供数据库的透明拆分及高可用服务.RadonDB包括Radon, Xenon, MySQL三部分安装.其 ...

  9. MySQL分库分表面试知识点

    目录 1 问题分析: 1.1 背景 1.2 业务分库 1.3 数据库分表 2 为什么要分库分表? 3 用过哪些分库分表中间件? 3.1 你们具体是如何对数据库如何进行垂直拆分或水平拆分的? 4 Mys ...

最新文章

  1. [洛谷P4721]【模板】分治 FFT
  2. Flask之WTForms验证
  3. 关于ssh 配置文件的参数说明
  4. .NET应用迁移到.NET Core--调查案例
  5. spring-bean版本_如何模拟Spring bean(版本2)
  6. Tomcat10 开机启动 Linux环境
  7. 【树莓派】树莓派(Debian)- root用户无法使用SSH登录
  8. 我如何将亿次的计算降为实时
  9. 百度万人协同规模下的代码管理架构演进
  10. 逻辑回归算法python_逻辑回归算法原理和例子
  11. JavaScript的实现
  12. PSP3000破解教程
  13. FastDFS文件上传
  14. python计算2的n次方编写_python中n次方怎么表示
  15. Unity 2D游戏制作流程用到的技巧
  16. 乘风领航、耀世创新——DEFI平台Lizard打造数字金融新世界
  17. 如何批量OCR识别各类票据关键信息,导出为结构化格式数据
  18. 前端 - 如何引入阿里巴巴矢量图库?
  19. Android 绘制圆形进度条
  20. Unity UGUI基础 之 Scroll View/Scroll Rect 的简单使用,并取消拖拽(滑动内容)效果,拖拽只在Scrollbar 上起作用

热门文章

  1. Linux命令——chmod
  2. 管理Apache服务器访问日志
  3. oracle11g安装和基本的使用,手把手看图教你用起来。
  4. 为什么 Dapr 如此令人兴奋
  5. 是否可以将 json 反序列化为 dynamic 对象?
  6. 方法参数修饰符in,out,ref
  7. 5月TIOBE编程榜,Java、PHP降级,C#再度上升!
  8. NET问答: 如何在 ASP.NET Core Web API 的 Response 中添加自定义的 Header ?
  9. 使用 Avalonia 开发 UOS 原生应用
  10. Flash 生命终止,HTML5能否完美替代?