通过 NewLife.XCode 迁移任意现有数据库到任意数据库(附分表分库方法)

  • 本文背景是将其他系统的数据库迁移到另一个数据库(仅需 20 行代码),也可以作为项目迁移用,生成自己系统的专属实体代码结构。比如将一些主流的数据库迁移到NewLife.XCode,开发基于 XCode 的项目
  • 其实只是想拿到别的数据库结构后,生成实体代码进行开发,顺便就更新一下大佬的利用 XCode 20 行代码搞定任何数据库的迁移

源数据库

  • 如果已有源数据库,请跳下一步
  • 此教程以 IdentityServer4 的 Ids4Adminis4admin模板为例
  • 首先安装模板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.CoreXCode的 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 迁移任意现有数据库到任意数据库的更多相关文章

  1. [NewLife.XCode]增删改查入门

    NewLife.XCode是一个有10多年历史的开源数据中间件,由新生命团队(2002~2019)开发完成并维护至今,以下简称XCode. 整个系列教程会大量结合示例代码和运行日志来进行深入分析,蕴含 ...

  2. 【原创】Newlife.XCode的常见功能使用(一)查询与数据初始化

            本博客所有文章分类的总目录:http://www.cnblogs.com/asxinyu/p/4288836.html Newlife XCode组件相关文章目录:http://www ...

  3. 【目录】Newlife XCode组件相关文章目录

    本博客所有文章分类的总目录链接:本博客博文总目录-实时更新  1.Newlife XCode组件相关文章目录  1.Newlife XCode组件资源目录汇总[2013年版]    2.Newlife ...

  4. [NewLife.XCode]实体类详解

    NewLife.XCode是一个有10多年历史的开源数据中间件,由新生命团队(2002~2019)开发完成并维护至今,以下简称XCode. 整个系列教程会大量结合示例代码和运行日志来进行深入分析,蕴含 ...

  5. [NewLife.XCode]功能设置

    NewLife.XCode是一个有10多年历史的开源数据中间件,由新生命团队(2002~2019)开发完成并维护至今,以下简称XCode. 整个系列教程会大量结合示例代码和运行日志来进行深入分析,蕴含 ...

  6. [NewLife.XCode]高级查询(化繁为简、分页提升性能)

    NewLife.XCode是一个有10多年历史的开源数据中间件,支持nfx/netcore,由新生命团队(2002~2019)开发完成并维护至今,以下简称XCode. 整个系列教程会大量结合示例代码和 ...

  7. [NewLife.XCode]百亿级性能

    NewLife.XCode是一个有10多年历史的开源数据中间件,支持nfx/netcore,由新生命团队(2002~2019)开发完成并维护至今,以下简称XCode. 整个系列教程会大量结合示例代码和 ...

  8. [NewLife.XCode]导入导出(实体对象百变魔君)

    NewLife.XCode是一个有10多年历史的开源数据中间件,支持nfx/netcore,由新生命团队(2002~2019)开发完成并维护至今,以下简称XCode. 整个系列教程会大量结合示例代码和 ...

  9. NewLife.XCode 上手指南

    想了解什么是XCode 在这里我不对XCode做过多介绍,XCode曾经是一个轻量级ORM组件,现在是一个重量级数据映射框架,支持实体对象数据到不同媒体的数据映射,提供面向对象的方式操作数据库,解决9 ...

随机推荐

  1. canvas和图片互转

    原文:http://www.jb51.net/html5/160920.html 这么神奇么?先记录一下. 使用JavaScript将图片拷贝进画布 要想将图片放入画布里,我们使用canvas元素的d ...

  2. Python之异常处理和socket套接字连接7

    一.异常处理 1)异常处理的使用意义 什么是异常处理 异常是程序发生错误的信号,即程序一旦出错就会立刻产生一个异常,如果该异常没有被处理 那么异常就抛出来,程序的运行也随之终止 异常分为三部分: 异常 ...

  3. 前端之javascript的节点操作和Event

    一 节点的增删改查 创建节点:createElement(标签名):创建一个指定名称的节点. 追加节点: 追加一个子节点:somenode.appendChild(标签名) 指定某个位置前面添加一个节 ...

  4. msyql 主从配置

    vim /etc/mysql/my.cnf; # 以下部分一定要配置在[mysqld]后面 [mysqld] log-bin=mysql-bin server-id= //设置数据库服务器唯一ID,这 ...

  5. 简单实现java线程池

    使用多线程以及线程池的意义无需多说,要想掌握线程池,最好的方法还是自己手动去实现. 一.实现思路      (网络盗图) 二.实现代码 1.线程池类 package com.ty.thread; im ...

  6. Codeforces Round #543 (Div. 2) D 双指针 + 模拟

    https://codeforces.com/contest/1121/problem/D 题意 给你一个m(<=5e5)个数的序列,选择删除某些数,使得剩下的数按每组k个数以此分成n组(n*k ...

  7. excel中vba将excel中数字和图表输出到word中

    参考:https://wenku.baidu.com/view/6c60420ecc175527072208af.html 比如将选区变为图片保存到桌面: Sub 将选区转为图片存到桌面() Dim ...

  8. Promise.all函数的使用

    Promise.all([this.getCity('guess'),this.getCity('hot'),this.getCity('group')]).then(res=>{ // con ...

  9. mysql学习之路_基础知识

                    Mysql php阶段将数据库分为三个阶 基础阶段: mysql数据库的基本操作(增删改查),以及一些高级操作(视图,触发器,函数,存储过程等),PHP操作没有sql数 ...

  10. 根据方法名获取方法Body Content

    利用 MethodBody类的GetILAsByteArray方法可以获取到返回字节数组的MSIL的body.然后再去解析此字节数组, 可以得到MSIL,然后你再去解析MSIL,你就可以得到你想到so ...