通过 NewLife.XCode 迁移任意现有数据库到任意数据库
通过 NewLife.XCode 迁移任意现有数据库到任意数据库(附分表分库方法)
- 本文背景是将其他系统的数据库迁移到另一个数据库(仅需 20 行代码),也可以作为项目迁移用,生成自己系统的专属实体代码结构。比如将一些主流的数据库迁移到NewLife.XCode,开发基于 XCode 的项目
- 其实只是想拿到别的数据库结构后,生成实体代码进行开发,顺便就更新一下大佬的利用 XCode 20 行代码搞定任何数据库的迁移
源数据库
- 如果已有源数据库,请跳下一步
- 此教程以 IdentityServer4 的 Ids4Admin 的
is4admin
模板为例 - 首先安装模板
dotnet new -i identityserver4.templates
,然后新建项目dotnet new is4admin
,运行项目dotnet run
,访问http://localhost:5000 - 运行一次项目之后会得到 sqlite 数据库文件,到这里我们就有了源数据库
码神工具 XCoder 生成实体代码
- 以下将完成模型文件的导出和实体类代码生成,对于表结构差别有点大,并且数据量不多的情况,可以生成实体类手动执行
读取数据->写入数据
的操作,这样可以执行一些数据手动处理。比如不仅是迁移,并且数据结构有一点变化,这样就可以从原数据库读取数据,处理后插入新的数据库。否则就采用反向工程获取表结构逆向生成新的数据库结构直接把数据从源数据库导入新数据库。
下载 XCoder
- 源码地址 https://github.com/NewLifeX/XCoder ,建议自己拉代码下来编译
- 编译完成后,在输出目录找到
XCoder.exe
,以及XCoder.exe.config
,双击打开运行码神工具
配置连接
- 打开
XCoder.exe.config
,添加数据库连接,码神工具内置了支持的数据库的连接写法,现在我们配置 sqlite 的连接,并把刚刚的IdentityServer.db
复制到码神工具目录
导出模型文件
- 打开码神工具,并依次打开工具-数据建模工具
- 在连接处选择刚刚配置的连接 IdentityServer 并点击连接,导出模型,即可得到 xml 模型文件
生成实体模型代码
- 新建控制台项目,Nuget 安装
NewLife.XCode
,如果没有 tt 文件,可从源码X 组件中获取,注意NewLife.Core
和XCode
的 dll 路径,如果不对的话可设置为当前目录,并将码神工具目录下的 dll 放到与 tt 文件相同目录
<#@ assembly name="NewLife.Core.dll" #>
<#@ assembly name="XCode.dll" #>
- 将刚导出的模型文件复制到与 tt 相同的目录,vs 中选中 tt 文件,选择右键运行自定义工具,稍等片刻,即可生成实体代码文件
迁移过程(重点)
- 主要是更新
http://www.cnblogs.com/asxinyu/archive/2012/04/26/2470838.html
对应的新版代码实现
利用反向工程
- 整个过程实际上就是先通过反向工程拿到表架构信息,再通过 XCode 内置的数据同步备份功能实现迁移,指定表架构信息和目标连接字符串即可
- 原理就是根据表架构信息和目标连接字符串,创建目标数据库表,并生成批量插入语句执行
- 这里可以选择单表备份或者多表
/// <summary>
/// 拷贝数据库,只需要数据库连接字符串和源数据库即可
/// </summary>
/// <param name="originConnName">源数据库连接字符串名</param>
/// <param name="desConnName">目的数据库连接字符串名</param>
/// <param name="perCount">每次获取的记录数目,如果默认-1则会自动调用函数计算一个合理值</param>
public static void CopyDataBase(string originConnName, string desConnName, int perCount = -1)
{
//思路:通过源数据库获取架构信息,然后反向工程,然后导出数据
var dal = DAL.Create(originConnName);
//获取源数据库的架构信息
var tableList = new List<IDataTable>();
// 处理表的架构信息
foreach (var dataTable in dal.Tables)
{
//过滤掉视图
if (dataTable.IsView) continue;
// 2019-03-31 处理列名称,后续版本会修复此bug,不用此处理
CheckColumnName(dataTable.Columns);
// 修正列的类型
FixColumns(dataTable.Columns);
tableList.Add(dataTable);
}
dal.SyncAll(tableList.ToArray(), desConnName, true);
}
/// <summary>
/// 兼容处理,类型转换
/// </summary>
/// <param name="columns"></param>
public static void FixColumns(List<IDataColumn> columns)
{
if (columns.Count < 1)
{
return;
}
var indexColumns = columns[0].Table.Indexes.SelectMany(s => s.Columns).ToArray();
foreach (var column in columns)
{
// 主键、唯一、类型为text的全部修改为varchar(50)
if ((!column.PrimaryKey &&
!indexColumns.Any(a => a.Equals(column.Name, StringComparison.OrdinalIgnoreCase))) ||
!column.RawType.Contains("text", StringComparison.OrdinalIgnoreCase)) continue;
column.RawType = "varchar(50)";
column.Length = 50;
}
}
public static void CheckColumnName(List<IDataColumn> columns)
{
foreach (var dataTableColumn in columns)
{
if (dataTableColumn.Name.IsNullOrWhiteSpace())
{
dataTableColumn.Name = dataTableColumn.ColumnName;
}
}
}
利用分表分库
- XCode 自带分表分库功能,而且很方便,通过自行指定目标数据库连接字符串,以及不同表名,可将数据保存到目标数据库,甚至不同分表
/// <summary>
/// 通过实体操作接口批量拷贝数据库,也可作为分表分库功能使用
/// </summary>
/// <param name="factory"></param>
/// <param name="originConnName"></param>
/// <param name="desConnName"></param>
/// <param name="perCount"></param>
public static void CopyDataTable(IEntityOperate factory, string originConnName, string desConnName, int perCount = -1)
{
//分页获取数据,并更新到新的数据库,通过更改数据库连接来完成
FixColumns(factory.Table.DataTable.Columns);
var allCount = factory.Count;
var tableName = factory.TableName;
if (perCount < 0) perCount = GetDataRowsPerConvert(allCount);
var pages = (int)Math.Ceiling((double)((double)allCount / (double)perCount));
for (var i = 0; i < pages; i++)
{
// 与上面代码等效
factory.ConnName = originConnName;
// factory.TableName = tableName;
var modelList = factory.FindAll(string.Empty, string.Empty, string.Empty, i * perCount, perCount);
factory.ConnName = desConnName;
// 分表分库的时候,这里可加上判断是否分表的逻辑,即可将数据保存到分表
// factory.TableName = tableName + DateTime.Now.ToString("yyyy-MM-dd");
modelList.Insert(true);
}
}
public static void ExcCopy(string originConnName, string desConnName, int perCount = -1)
{
var asm = typeof(ApiClaims).Assembly;
var entityTypes = asm.GetTypes().Where(s => typeof(EntityBase).IsAssignableFrom(s)).ToArray();
foreach (var entityType in entityTypes)
{
var factory = entityType.AsFactory();
CopyDataTable(factory, originConnName, desConnName, perCount);
}
}
/// <summary>
/// 兼容处理,类型转换
/// </summary>
/// <param name="columns"></param>
public static void FixColumns(List<IDataColumn> columns)
{
if (columns.Count < 1)
{
return;
}
var indexColumns = columns[0].Table.Indexes.SelectMany(s => s.Columns).ToArray();
foreach (var column in columns)
{
// 主键、唯一、类型为text的全部修改为varchar(50)
if ((!column.PrimaryKey &&
!indexColumns.Any(a => a.Equals(column.Name, StringComparison.OrdinalIgnoreCase))) ||
!column.RawType.Contains("text", StringComparison.OrdinalIgnoreCase)) continue;
column.RawType = "varchar(50)";
column.Length = 50;
}
}
public static void CheckColumnName(List<IDataColumn> columns)
{
foreach (var dataTableColumn in columns)
{
if (dataTableColumn.Name.IsNullOrWhiteSpace())
{
dataTableColumn.Name = dataTableColumn.ColumnName;
}
}
}
/// <summary>
/// 根据数据表的记录总数来设置一个合理的每次转换数目。数据量大,一次性导出导入不合理
/// </summary>
/// <param name="allCount">数据表记录总数</param>
/// <returns>每次转换的记录数</returns>
private static int GetDataRowsPerConvert(int allCount)
{
if (allCount < 1000) return 200;
else if (allCount < 5000) return 500;
else if (allCount < 50000) return 1000;
else return 1500;
}
结果
- 以表
ClientScopes
的结果为例 - 反向功能同步,由于事先全部逆向创建所有表了,所以这里只有同步时的插入语句
0:53:55.258 1 N - Select * From ClientScopes limit 10000
10:53:55.258 1 N - 同步[ClientScopes/identityServer_sqlite]数据 0 + 3
10:53:55.270 27 N 19 Insert Into ClientScopes(Id,Scope,ClientId) Values(1,'admin_ui_profile',1),(2,'admin_api',1),(3,'openid',1)
10:53:55.272 1 N - 同步[ClientScopes/identityServer_sqlite]完成,共[3]行,耗时14ms,速度209tps
- 分表分库同步,这里是通过反射遍历所有表的类型,通过实体操作接口一个个表同步,所以是建一个表同步一个表的数据
12:27:33.896 1 N - ClientScopes.Count 快速计算表记录数(非精确)[ClientScopes/identityServer_sqlite] 参考值 -2
12:27:33.896 1 N - Select * From ClientScopes Order By Id Desc limit 1
12:27:33.896 1 N - Select Count(*) From ClientScopes
12:27:33.897 1 N - Select * From ClientScopes Order By Id Desc limit 200
12:27:33.898 9 Y 33 SHOW TABLE STATUS FROM `IdentityServer`
12:27:33.899 9 Y 33 创建表:ClientScopes()
12:27:33.899 9 Y 33 Create Table If Not Exists ClientScopes(
Id INT NOT NULL AUTO_INCREMENT,
Scope TEXT NOT NULL,
ClientId BIGINT NOT NULL,
Primary Key (Id)
) DEFAULT CHARSET=utf8mb4;
12:27:33.902 9 Y 33 Create Index IX_ClientScopes_ClientId On ClientScopes (ClientId)
12:27:33.913 1 N - Insert Into ClientScopes(Id,Scope,ClientId) Values(3,'openid',1),(2,'admin_api',1),(1,'admin_ui_profile',1)
遇到的问题
每次的不顺都伴随 bug 的发现,此次第一个问题是报索引超出数组范围,原因是数据库会话根据列名称获取序号时,列名称 Name 为空,此处是通过源数据库获取的表结构信息列名 Name 为空,而通过实体类获取的列名 Name 才有值,所以上面代码多了一段处理列名的操作
第二个问题发生于创建 mysql 表时,报
BLOB/TEXT column 'CustomerName' used in key specification without a key length
。这种错误有两处,一处是创建索引,比如 Name 是 text 类型,在 mysql 里面不能指明长度,而索引只能索引固定长度的字符,或者前多少个字符,所以要将 text 类型改成 varchar 类型。另一处就是 id 也是 text 类型,作为主键,唯一,在 mysql 中也不允许没有指定长度,还是得改
总结
- 例子源码
- 代码不规范,维护两行泪,迁移过程的主要问题还是表设计的问题,我们设计表的过程尽量用通用的数据类型,这样在跨数据库的时候方便转换。同时根据需求尽量设计合理一点,比如作为主键的 id,如果设计为 text 类型,没有长度限制,或许在某个数据库可以,另一个数据库里却有问题。中间可能涉及存储空间、查找和索引的等(性能)问题
- 常用数据类型有 int、nvarchar、ntext、decimal 和 datetime2
通过 NewLife.XCode 迁移任意现有数据库到任意数据库的更多相关文章
- [NewLife.XCode]增删改查入门
NewLife.XCode是一个有10多年历史的开源数据中间件,由新生命团队(2002~2019)开发完成并维护至今,以下简称XCode. 整个系列教程会大量结合示例代码和运行日志来进行深入分析,蕴含 ...
- 【原创】Newlife.XCode的常见功能使用(一)查询与数据初始化
本博客所有文章分类的总目录:http://www.cnblogs.com/asxinyu/p/4288836.html Newlife XCode组件相关文章目录:http://www ...
- 【目录】Newlife XCode组件相关文章目录
本博客所有文章分类的总目录链接:本博客博文总目录-实时更新 1.Newlife XCode组件相关文章目录 1.Newlife XCode组件资源目录汇总[2013年版] 2.Newlife ...
- [NewLife.XCode]实体类详解
NewLife.XCode是一个有10多年历史的开源数据中间件,由新生命团队(2002~2019)开发完成并维护至今,以下简称XCode. 整个系列教程会大量结合示例代码和运行日志来进行深入分析,蕴含 ...
- [NewLife.XCode]功能设置
NewLife.XCode是一个有10多年历史的开源数据中间件,由新生命团队(2002~2019)开发完成并维护至今,以下简称XCode. 整个系列教程会大量结合示例代码和运行日志来进行深入分析,蕴含 ...
- [NewLife.XCode]高级查询(化繁为简、分页提升性能)
NewLife.XCode是一个有10多年历史的开源数据中间件,支持nfx/netcore,由新生命团队(2002~2019)开发完成并维护至今,以下简称XCode. 整个系列教程会大量结合示例代码和 ...
- [NewLife.XCode]百亿级性能
NewLife.XCode是一个有10多年历史的开源数据中间件,支持nfx/netcore,由新生命团队(2002~2019)开发完成并维护至今,以下简称XCode. 整个系列教程会大量结合示例代码和 ...
- [NewLife.XCode]导入导出(实体对象百变魔君)
NewLife.XCode是一个有10多年历史的开源数据中间件,支持nfx/netcore,由新生命团队(2002~2019)开发完成并维护至今,以下简称XCode. 整个系列教程会大量结合示例代码和 ...
- NewLife.XCode 上手指南
想了解什么是XCode 在这里我不对XCode做过多介绍,XCode曾经是一个轻量级ORM组件,现在是一个重量级数据映射框架,支持实体对象数据到不同媒体的数据映射,提供面向对象的方式操作数据库,解决9 ...
随机推荐
- Spring IOC(一)体系结构
Spring IOC(一)体系结构 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) BeanFactory 是Spring ...
- jquery动态出操作select
var citys = {1:'北京',2:'上海',3:'广州',4:'深圳'}; $("#city option:gt(0)").remove(); for(var k in ...
- wireshark源码分析二
一.源代码结构 在wireshark源代码根目录下,可以看到以下子目录: 1)物理结构 其中,epan文件夹负责所有网络协议识别工作,plugins里面存放了wireshark所有插件,gtk ...
- 【转】VxWorks中高精度实时时钟的实现及C语言汇编混合编程
最近一个项目中需要在VxWorks下使用一个高精度实时时钟,要求精度为1ms,溢 出时间大于5小时.VxWorks提供系统时钟,该时钟在操作系统启动后开始计数,精度为1个tick,可以通过tickGe ...
- windows10安装ubuntu on windows10
安装向导 https://msdn.microsoft.com/en-us/commandline/wsl/install_guide
- mybatis拦截器案例之获取结果集总条数
最近做的项目前端是外包出去的,所以在做查询分页的时候比较麻烦 我们需要先吧结果集的条数返回给前端,然后由前端根据页面情况(当前页码,每页显示条数)将所需参数传到后端. 由于在项目搭建的时候,是没有考虑 ...
- idea创建spring boot+mybatis(oracle)+themeleaf项目
1.新建项目 选择idea已经有的spring initializr next,然后填写项目命名,包名 然后next,选择所需要的依赖 然后一路next,finish,项目新建成功,然后可以删除下面的 ...
- 2019.01.14 codeforces685B. Kay and Snowflake(树形dp)
传送门 题意简述:给出一棵树,求每个子树的重心. 首先通过画图可以观察出一个性质,我们从叶子结点向根节点递推重心的话重心的位置是不会下降的. 然后由于一个点的重心要么是自己,要么在重儿子子树内,因此如 ...
- 2019.01.02 洛谷P4512 【模板】多项式除法
传送门 解析 代码: #include<bits/stdc++.h> #define ri register int using namespace std; typedef long l ...
- 在table中tr的display:block在firefox下显示布局错乱问题
[转自:] http://blog.csdn.net/sd2131512/article/details/4720345 按照常理,对于某一单元行需要显示时,使用:display:block属性,不需 ...