使用EntityFrameworkCore实现Repository, UnitOfWork,支持MySQL分库分表

昨天(星期五)下班,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跨平台或扫描二维码关注

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

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

相关文章

cs1.5 linux服务端,Linux下架设CS1.5服务器

所需文件&#xff1a;1、hlds_l_3110_full.bin2、cs_15_full.tar.gz3、engine_i386.so(据说这个东东可以免cdkey&#xff0c;可我不用也可免cdkey)把所有的文件都拷贝到/usr/cs目录下&#xff1a;# chmod x hlds_l_3110_full.bin# tar xzvf hlds_l_3110_full.tar.gz# tar xzvf …

MySQL isnull()函数基本指南

转载自 MySQL isnull()函数基本指南 MySQL ISNULL函数简介 ISNULL函数接受一个参数&#xff0c;并测试该参数是否为NULL。如果参数为NULL&#xff0c;则ISNULL函数返回1&#xff0c;否则返回0。 下面说明了ISNULL函数的语法&#xff1a; ISNULL(expr)请考虑以下示例&#…

ppt2010基础操作笔记

一、PPT2010的界面&#xff1a; 1.功能选项卡 2.大纲区 3.标题栏 4.备注页 5.状态栏 6.工作区 二、创建PPT演示文稿 1.开始–》PPT2010 2.在PPT里面新建–》文件–》新建–》样本模板–》选择模板。 三、PPT2010中的视图&#xff1a; 1.普通视图 2.大纲视图 3.幻灯…

2019蓝桥杯省赛---java---C---2(矩阵切割)

题目描述 代码实现 package TEST;import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner sc new Scanner(System.in);int max sc.nextInt();int min sc.nextInt();int sum 0, temp;while (max ! 0 && min ! 0) {if (max …

期待微软平台即服务技术Service Fabric 开源

微软的Azure Service Fabric的官方博客在3.24日发布了一篇博客 Service Fabric .NET SDK goes open source &#xff0c;介绍了社区呼声最高的Service Fabric开源的情况以及当前的情况&#xff0c;这次开源了Service Fabric的.NET SDK部分&#xff0c;主要是两个&#xff1a; …

java中input表单中的type=date返回类型【切记】

input表单中的type”date”返回类型为Date类型&#xff01; input表单中的type”date”返回类型为Date类型&#xff01; input表单中的type”date”返回类型为Date类型&#xff01; 重要的内容说三遍&#xff0c;今晚可算是及坑死我了&#xff0c;一直以为是String类型 &…

全自动安装 linux光盘,CentOS 7.1全自动安装光盘制作详解

安装系统的话&#xff0c;常用方式就是通过U盘&#xff0c;光盘&#xff0c;kickstart网络自动化安装&#xff0c;网络自动化安装固然好&#xff0c;但是有时候在现场做项目的话根本就没有kickstart环境&#xff0c;甚至连基本的网络建设都没有弄好&#xff0c;此时该如何安装系…

MySQL last_insert_id()函数

转载自 MySQL last_insert_id()函数 MySQL LAST_INSERT_ID函数简介 在数据库设计中&#xff0c;我们经常使用代理键使用AUTO_INCREMENT属性为主键列生成唯一的整数值。 当您将新行插入到具有AUTO_INCREMENT列的表中时&#xff0c;MySQL会自动生成一个唯一的整数&#xff0c…

2019蓝桥杯省赛---java---C---4(质数)

题目描述 代码实现 package TEST; public class Main {public static void main(String[] args) {int cnt 0;for (int i 2; ; i )if(check(i)){cnt ;if(cnt 2019){System.out.println(i);break;}}}public static boolean check(int n){//判断一个数是否为质数for (int i 2…

eclipse下载与安装步骤详解,包含解决错误(最全最详细)

以前一直用的是myeclipse,今天有幸接触eclipse,那我们就先来安装的配置一下&#xff0c;下载地址&#xff1a;点击下载密码&#xff1a;h0kg&#xff0c;下载完成以后就可以安装了&#xff0c;首先我们来先安装jdk1.7, 打开jdk的安装包 双击即可&#xff0c; 直接点击下一步&…

CoreCLR文档翻译 - GC的设计

此文档来源于CoreCLR的BOTR(The Book of the Runtime), 点击打开原文一切著作权归微软公司所有 GC的设计 作者: Maoni Stephens (maoni0) - 2015 提示: 推荐看 The Garbage Collection Handbook 这本书学习更多关于GC的知识 (在文章底部的链接中) 组件结构 在GC中有两个主…

MySQL cast()函数

转载自 MySQL cast()函数 MySQL CAST函数介绍 MySQL CAST()函数的语法如下&#xff1a; CAST(expression AS TYPE);CAST()函数将任何类型的值转换为具有指定类型的值。目标类型可以是以下类型之一&#xff1a;BINARY&#xff0c;CHAR&#xff0c;DATE&#xff0c;DATETIME…

linux 编译 expat,关于expat库的编译

1、expat库简介refer to wiki:To use the Expat library, programs first register handler functions with Expat. When Expat parses an XML document, it calls the registered handlers as it finds relevant tokens in the input stream. These tokens and their associat…

node.js安装不好怎么办

试试重启电脑 可能解决了百分之99.9的问题

计算机操作基础

一、计算机硬件组成&#xff1a; 1.CPU 2.主板 3.内存 4.硬盘 5.光驱 6.显卡 7.网卡 8.声卡 9.电源 10.操作系统 二、CPU型号&#xff1a; 1.INTEL 1)主频&#xff1a;2.8-3.7GHZ 2)核心&#xff1a;双核、四核、八核、十核 3)高速总缓冲&#xff1a;2-20MB 4)…

CoreCLR源码探索(四) GC内存收集器的内部实现 分析篇

在这篇中我将讲述GC Collector内部的实现, 这是CoreCLR中除了JIT以外最复杂部分&#xff0c;下面一些概念目前尚未有公开的文档和书籍讲到。 为了分析这部分我花了一个多月的时间&#xff0c;期间也多次向CoreCLR的开发组提问过&#xff0c;我有信心以下内容都是比较准确的&am…

vue开源项目

转载自 vue开源项目 一、前台UI组件库 1.Element 优点&#xff1a;中文文档&#xff0c;ui种类比较全&#xff0c;ui设计简洁清晰 缺点&#xff1a;不够有特点 2.iView 优点&#xff1a;和element的UI很相似&#xff0c;有一些多的补充&#xff0c;可以相互替换 缺点&am…

linux跑循环脚本占内存,Linux下实现脚本监测特定进程占用内存情况

Linux系统下&#xff0c;我们可以利用以下命令来获取特定进程的运行情况&#xff1a;cat /proc/$PID/status其中PID是具体的进程号&#xff0c;这个命令打印出/proc/特定进程/status文件的内容&#xff0c;信息比较多&#xff0c;包含了物理内存/虚拟内存的使用状况&#xff0c…

如何在vm虚拟机里面安装win10操作系统

首先打开虚拟机&#xff0c;点击创建虚拟机 然后选择典型即可&#xff01; 选择稍后安装操作系统 然后选择win10 64位 . 然后在找个路径&#xff1a; 默认60GB即可&#xff0c;也可以更改大小&#xff1a; 最后点击完成&#xff1a; 接下来我们需要用U盘制作一个启动盘…