使用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,一经查实,立即删除!

相关文章

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; …

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中有两个主…

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盘制作一个启动盘…

CoreCLR源码探索(五) GC内存收集器的内部实现 调试篇

在上一篇中我分析了CoreCLR中GC的内部处理&#xff0c; 在这一篇我将使用LLDB实际跟踪CoreCLR中GC&#xff0c;关于如何使用LLDB调试CoreCLR的介绍可以看: 微软官方的文档&#xff0c;地址我在第3篇中的介绍&#xff0c;地址LLDB官方的入门文档&#xff0c;地址 源代码 本篇…

小米路由器青春版装linux,比较费心的折腾 篇二:小米路由器青春版折腾负载均衡...

比较费心的折腾 篇二&#xff1a;小米路由器青春版折腾负载均衡2020-06-04 11:00:233点赞10收藏8评论创作立场声明&#xff1a;本文的核心目的是对于比较久远的硬件进行折腾&#xff0c;提及的软件可以在官网、软件包或者github找到。开篇预警本文截图较多&#xff0c;但是前期…

2019蓝桥杯省赛---java---C---6(旋转)

题目描述 问题描述图片旋转是对图片最简单的处理方式之一&#xff0c;在本题中&#xff0c;你需要对图片顺时 针旋转 90 度。 我们用一个 nm 的二维数组来表示一个图片&#xff0c;例如下面给出一个 34 的 图片的例子&#xff1a;1 3 5 7 9 8 7 6 3 5 9 7这个图片顺时针旋转 9…

linux container 原理,容器概念与Linux Container原理

一、容器与LxC在像KVM等众多主机虚拟化解决方案中&#xff0c;对每一个虚拟机实例提供的是从底层硬件开始一直到上层的环境&#xff0c;在硬件级进行资源划分。虚拟机的内核是运行在硬件内核之上的。由于每个虚拟实例都有自己的运行内核&#xff0c;所以各实例之间有非常好的隔…

微软建议Windows 10开发人员升级到Visual Studio 2017

既然Visual Studio 2017已经发布&#xff0c;那就意味着微软开始专注于让其成为默认开发平台。Creators Update SDK的发布&#xff08;面向即将到来的Windows 10 Creators Update&#xff09;就是这种转变的一个很好的例子。该SDK只有Visual Studio 2017支持。 幸运的是&#…

p2p linux 开源项目,权威开源项目(linux系统、sip、live555)

1、IT知识交流(语言工具、系统问题、开源项目交流)http://stackoverflow.com/tags2、linux系统www.kernel.orghttp://www.gnu.org/copyleft/lesser.html3、pjsuahttp://www.pjsip.org/1、freeswitch1、opensipshttp://opensips.org/pub/opensips/4、live555http://www.live555.…

在Visual Studio中使用任何C++编译器

原文发表时间: 3/07/2017原文发表地址: Use any C Compiler with Visual Studio 微软Visual Studio 2017支持几种C编译器以适应各种各样的代码库。除了很多人熟悉的微软Visual C编译器外&#xff0c; Visual Studio2017还支持Clang, GCC以及其他针对某些平台的编辑器。 这篇文…

2020蓝桥杯省赛---java---B---8(数字三角形)

题目描述 时间限制: 1.0s 内存限制: 512.0MB 本题总分&#xff1a;20 分【问题描述】上图给出了一个数字三角形。从三角形的顶部到底部有很多条不同的路径。 对于每条路径&#xff0c;把路径上面的数加起来可以得到一个和&#xff0c;你的任务就是找到最 大的和。路径上的每一…

ASP.NET Core MVC 源码学习:Routing 路由

前言 最近打算抽时间看一下 ASP.NET Core MVC 的源码&#xff0c;特此把自己学习到的内容记录下来&#xff0c;也算是做个笔记吧。 路由作为 MVC 的基本部分&#xff0c;所以在学习 MVC 的其他源码之前还是先学习一下路由系统&#xff0c;ASP.NET Core 的路由系统相对于以前的…

linux netfilter 过滤数据包,Netfilter-iptabes报文过滤框架(一)

什么是Netfilter/iptableNetfilter/iptables是Linux内核内置的报文过滤框架&#xff0c;程序可以通过该框架完成报文过滤、地址转换(NAT)以及连接跟踪等功能。Netfilter/iptables由两部分组成&#xff0c;一部分是Netfilter的"钩子(hook)"&#xff0c;这些"钩子…