28

实现业务数据的同步迁移 · 思路一

 3 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzAwNTMxMzg1MA%3D%3D&%3Bmid=2654079185&%3Bidx=7&%3Bsn=2887774f918f0bd5be35ee409c44210b
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

ey2uiqZ.png!web

(好雨知时节,大雨 _ _ _)

时不时的呢,会有小伙伴问我这样的问题:

1、群主,你的.tsv文件是如何生成的?

2、在线项目数据和种子数据的不一样,可以下么?

3、如果我本地的数据开发好了,如何把新的数据迁到生产环境呢?

0 1

PART

设计思路

这几个问题还是问了一段时间后,我感觉是时候需要考虑考虑了,之前一直比较懒或者没有很好的办法去处理这个问题,其实 今天的办法也不是最完美的,所以我叫思路一 ,如果有好的思路欢迎留言和建议,有奖励哟。

今天就暂时先说说这个简单的方案吧,比较简单,就是把数据从一个DB,迁到另一个DB,然后增加一个输出tsv的功能,看似很简单,还是用到了一些知识点的:

1、 多表联合 ,这个是基础,任何ORM都支持;

2、 读写分离 ,但是有2个前提,下文会具体说;

3、 事务处理 ,保证数据一致性嘛;

那下边就具体说说,如何来实现

0 2

PART

开发流程

代码不是很多,相信一遍就能看懂。

1、获取集合内完整数据

这里用到了多表联合查询,毕竟SqlSugar不像EFCore那样,可以一次性就把子属性给全部查询出来,感觉就像聚合一样,那在SqlSugar中的写法有两种, 官方默认的是Mapper好一些

/// <summary>

/// 查询出角色-菜单-接口关系表全部Map属性数据

/// </summary>

/// <returns></returns>

public async Task<List<RoleModulePermission>> GetRMPMaps()

{

return await Db.Queryable<RoleModulePermission>()

.Mapper(rmp => rmp.Module, rmp => rmp.ModuleId)

.Mapper(rmp => rmp.Permission, rmp => rmp.PermissionId)

.Mapper(rmp => rmp.Role, rmp => rmp.RoleId)

.Where(d => d.IsDeleted == false)

.ToListAsync();

}

PS:这里我不想再讨论各种ORM的孰优孰劣了,那是小孩纸才会干的事儿,我项目EFCore也用,Dapper也会,就酱吧。

2、开启数据库读写分离模式

既然要数据库迁移,肯定是需要一个DB转移到另一个DB,因为我们的项目正好已经实现了读写分离模式,那正好利用这个机制, 主库为写,所以配置为新库,从库为读,所以配置为旧库 。结果是这样的:

EJf26nA.png!web

这里要注意四点:

1、既然要迁移数据,那新库只生成表结构就行,不用初始化数据,False;

2、设置主库的ConnID;

3、开启CQRSEnabled开关,并配置主从库地址;

4、主从库数据库类型一致,不然会报错,毕竟不是多库模式;

千万记得新库是用来写的,所以是主库。

那最后启动项目结果是这样的:

AvYB73m.png!web

3、开始迁移

万事俱备,只欠东风了,这一步就是要迁移数据逻辑了。其实整个项目核心的就是权限聚合部分了,涉及到了四个表:

角色表、菜单表、接口表、关系表。

因为 系统用的是整型的自增主键ID,所以要考虑好关系表中,rid、mid、pid的值,要与对应表的id是一致的,如果你一直用的的GUID字符串的话,就不用考虑这个问题,无脑的数据迁移就行.

那现在要保证关系表的id问题,我是这么写的,在MigrateController.cs中:

/// <summary>

/// 获取权限部分Map数据(从库)

/// 迁移到新库(主库)

/// </summary>

/// <returns></returns>

[HttpGet]

public async Task<MessageModel<string>> DataMigrateFromOld2New()

{

var data = new MessageModel<string>() { success = true, msg = "" };

if (_env.IsDevelopment())

{

try

{

// 获取权限集合数据

var rmps = await _roleModulePermissionServices.GetRMPMaps();

// 当然,你可以做个where查询

//rmps = rmps.Where(d => d.ModuleId > 88).ToList();


// 开启事务,保证数据一致性

_unitOfWork.BeginTran();


var rid = 0;

var pid = 0;

var mid = 0;

var rpmid = 0;


// 注意信息的完整性,不要重复添加,确保主库没有要添加的数据

foreach (var item in rmps)

{

// 角色信息,防止重复添加,做了判断

if (item.Role != null)

{

var isExit = (await _roleServices.Query(d => d.Name == item.Role.Name && d.IsDeleted == false)).FirstOrDefault();

if (isExit == null)

{

rid = await _roleServices.Add(item.Role);

Console.WriteLine($"Role Added:{item.Role.Name}");

}

else

{

rid = isExit.Id;

}

}


// 菜单

if (item.Permission != null)

{

pid = await _permissionServices.Add(item.Permission);

Console.WriteLine($"Permission Added:{item.Permission.Name}");

}


// 接口

if (item.Module != null)

{

mid = await _moduleServices.Add(item.Module);

Console.WriteLine($"Module Added:{item.Module.LinkUrl}");

}


// 关系

if (rid > 0 && pid > 0 && mid > 0)

{

rpmid = await _roleModulePermissionServices.Add(new RoleModulePermission()

{

IsDeleted = false,

CreateTime = DateTime.Now,

ModifyTime = DateTime.Now,

ModuleId = mid,

PermissionId = pid,

RoleId = rid,

});

Console.WriteLine($"RMP Added:{rpmid}");

}


}



_unitOfWork.CommitTran();


data.success = true;

data.msg = "导入成功!";

}

catch (Exception)

{

_unitOfWork.RollbackTran();

}

}

else

{

data.success = false;

data.msg = "当前不处于开发模式,代码生成不可用!";

}


return data;

}

逻辑很简单,就是获取到整体数据后,一个个添加到新库里,然后再添加关系表,保证数据的完整性,然后用事务,如果出错,可以回滚,保证一致性。

4、查看结果

到了这里,基本就没有问题了,可以看到数据已经完成了迁移:

rYVVvae.png!web

(迁移过程,输出到控制台)

BzyE7rZ.png!web

(数据库查看新库,已经有了数据)

这里完全不用胆小你的生产数据库是否已经有数据了,无论有没有,添加的权限关系表的id,也一定会和三个子表是一一对应的,且id自增,没问题。

关于其他用户表,博客表肯定不需要迁移吧,这些本地环境肯定是没有的。

那迁移完了数据,如何生成到tsv文件里呢,请往下看。

0 3

PART

输出到文件

那现在我们的新库有了数据,我们就可以切换到单库模式来从新库里获取数据,然后生成到tsv文件里

BjEb2az.png!web

[HttpGet]

public async Task<MessageModel<string>> SaveData2TsvAsync()

{

var data = new MessageModel<string>() { success = true, msg = "" };

if (_env.IsDevelopment())

{

try

{

// 取出数据,序列化,自己可以处理判空

var rolesJson = JsonConvert.SerializeObject(await _roleServices.Query(d => d.IsDeleted == false));

FileHelper.WriteFile(Path.Combine(_env.WebRootPath, "BlogCore.Data.json", "Role_New.tsv"), rolesJson, Encoding.UTF8);



var permissionsJson = JsonConvert.SerializeObject(await _permissionServices.Query(d => d.IsDeleted == false));

FileHelper.WriteFile(Path.Combine(_env.WebRootPath, "BlogCore.Data.json", "Permission_New.tsv"), permissionsJson, Encoding.UTF8);



var modulesJson = JsonConvert.SerializeObject(await _moduleServices.Query(d => d.IsDeleted == false));

FileHelper.WriteFile(Path.Combine(_env.WebRootPath, "BlogCore.Data.json", "Modules_New.tsv"), modulesJson, Encoding.UTF8);



var rmpsJson = JsonConvert.SerializeObject(await _roleModulePermissionServices.Query(d => d.IsDeleted == false));

FileHelper.WriteFile(Path.Combine(_env.WebRootPath, "BlogCore.Data.json", "RoleModulePermission_New.tsv"), rmpsJson, Encoding.UTF8);

}

catch (Exception)

{

}



data.success = true;

data.msg = "生成成功!";

}

else

{

data.success = false;

data.msg = "当前不处于开发模式,代码生成不可用!";

}


return data;

}

结果我就不展示了,自己试试就可以了。

思考与总结

从上边的代码中,我们可以看出来,因为框架已经集成了很多重要的功能,比如 读写分离和事务 处理,所以代码还是比较简单的,如果自己从0开始写,还是比较麻烦的。

现在还有一个问题需要思考下, 如果实现不同类型数据库的生成,这里也是两种办法:

1、使用框架的多库模式,先从库1获取数据,然后切换数据库,再生成到库2;

2、可以生成到tsv文件里做个跳板,这不过这里有一个问题,就是关系表的id如果不一样,一定会混乱的,所以这个时候又说到了主键用INT还是GUID的问题了,自己处理吧。

还是欢迎大家多多提意见吧,如何对业务数据进行同步迁移,是一个好课题。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK