(好雨知時(shí)節(jié),大雨 _ _ _)
時(shí)不時(shí)的呢,會(huì)有小伙伴問我這樣的問題:
1、群主,你的.tsv文件是如何生成的?
2、在線項(xiàng)目數(shù)據(jù)和種子數(shù)據(jù)的不一樣,可以下么?
3、如果我本地的數(shù)據(jù)開發(fā)好了,如何把新的數(shù)據(jù)遷到生產(chǎn)環(huán)境呢?
01
PART
設(shè)計(jì)思路
這幾個(gè)問題還是問了一段時(shí)間后,我感覺是時(shí)候需要考慮考慮了,之前一直比較懶或者沒有很好的辦法去處理這個(gè)問題,其實(shí)今天的辦法也不是最完美的,所以我叫思路一,如果有好的思路歡迎留言和建議,有獎(jiǎng)勵(lì)喲。
今天就暫時(shí)先說說這個(gè)簡單的方案吧,比較簡單,就是把數(shù)據(jù)從一個(gè)DB,遷到另一個(gè)DB,然后增加一個(gè)輸出tsv的功能,看似很簡單,還是用到了一些知識(shí)點(diǎn)的:
1、多表聯(lián)合,這個(gè)是基礎(chǔ),任何ORM都支持;
2、讀寫分離,但是有2個(gè)前提,下文會(huì)具體說;
3、事務(wù)處理,保證數(shù)據(jù)一致性嘛;
那下邊就具體說說,如何來實(shí)現(xiàn)。
02
PART
開發(fā)流程
代碼不是很多,相信一遍就能看懂。
1、獲取集合內(nèi)完整數(shù)據(jù)
這里用到了多表聯(lián)合查詢,畢竟SqlSugar不像EFCore那樣,可以一次性就把子屬性給全部查詢出來,感覺就像聚合一樣,那在SqlSugar中的寫法有兩種,官方默認(rèn)的是Mapper好一些:
/// <summary>
/// 查詢出角色-菜單-接口關(guān)系表全部Map屬性數(shù)據(jù)
/// </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的孰優(yōu)孰劣了,那是小孩紙才會(huì)干的事兒,我項(xiàng)目EFCore也用,Dapper也會(huì),就醬吧。
2、開啟數(shù)據(jù)庫讀寫分離模式
既然要數(shù)據(jù)庫遷移,肯定是需要一個(gè)DB轉(zhuǎn)移到另一個(gè)DB,因?yàn)槲覀兊捻?xiàng)目正好已經(jīng)實(shí)現(xiàn)了讀寫分離模式,那正好利用這個(gè)機(jī)制,主庫為寫,所以配置為新庫,從庫為讀,所以配置為舊庫。結(jié)果是這樣的:
這里要注意四點(diǎn):
1、既然要遷移數(shù)據(jù),那新庫只生成表結(jié)構(gòu)就行,不用初始化數(shù)據(jù),False;
2、設(shè)置主庫的ConnID;
3、開啟CQRSEnabled開關(guān),并配置主從庫地址;
4、主從庫數(shù)據(jù)庫類型一致,不然會(huì)報(bào)錯(cuò),畢竟不是多庫模式;
千萬記得新庫是用來寫的,所以是主庫。
那最后啟動(dòng)項(xiàng)目結(jié)果是這樣的:
3、開始遷移
萬事俱備,只欠東風(fēng)了,這一步就是要遷移數(shù)據(jù)邏輯了。其實(shí)整個(gè)項(xiàng)目核心的就是權(quán)限聚合部分了,涉及到了四個(gè)表:
角色表、菜單表、接口表、關(guān)系表。
因?yàn)?strong>系統(tǒng)用的是整型的自增主鍵ID,所以要考慮好關(guān)系表中,rid、mid、pid的值,要與對(duì)應(yīng)表的id是一致的,如果你一直用的的GUID字符串的話,就不用考慮這個(gè)問題,無腦的數(shù)據(jù)遷移就行.
那現(xiàn)在要保證關(guān)系表的id問題,我是這么寫的,在MigrateController.cs中:
/// <summary>/// 獲取權(quán)限部分Map數(shù)據(jù)(從庫)/// 遷移到新庫(主庫)/// </summary>/// <returns></returns>[HttpGet]public async Task<MessageModel<string>> DataMigrateFromOld2New(){var data = new MessageModel<string>() { success = true, msg = "" };if (_env.IsDevelopment()){try{// 獲取權(quán)限集合數(shù)據(jù) var rmps = await _roleModulePermissionServices.GetRMPMaps();// 當(dāng)然,你可以做個(gè)where查詢//rmps = rmps.Where(d => d.ModuleId > 88).ToList();// 開啟事務(wù),保證數(shù)據(jù)一致性_unitOfWork.BeginTran();var rid = 0;var pid = 0;var mid = 0;var rpmid = 0;// 注意信息的完整性,不要重復(fù)添加,確保主庫沒有要添加的數(shù)據(jù)foreach (var item in rmps){// 角色信息,防止重復(fù)添加,做了判斷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}");}// 關(guān)系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 = "導(dǎo)入成功!";}catch (Exception){_unitOfWork.RollbackTran();}}else{data.success = false;data.msg = "當(dāng)前不處于開發(fā)模式,代碼生成不可用!";}return data;}
邏輯很簡單,就是獲取到整體數(shù)據(jù)后,一個(gè)個(gè)添加到新庫里,然后再添加關(guān)系表,保證數(shù)據(jù)的完整性,然后用事務(wù),如果出錯(cuò),可以回滾,保證一致性。
4、查看結(jié)果
到了這里,基本就沒有問題了,可以看到數(shù)據(jù)已經(jīng)完成了遷移:
(遷移過程,輸出到控制臺(tái))
(數(shù)據(jù)庫查看新庫,已經(jīng)有了數(shù)據(jù))
這里完全不用膽小你的生產(chǎn)數(shù)據(jù)庫是否已經(jīng)有數(shù)據(jù)了,無論有沒有,添加的權(quán)限關(guān)系表的id,也一定會(huì)和三個(gè)子表是一一對(duì)應(yīng)的,且id自增,沒問題。
關(guān)于其他用戶表,博客表肯定不需要遷移吧,這些本地環(huán)境肯定是沒有的。
那遷移完了數(shù)據(jù),如何生成到tsv文件里呢,請(qǐng)往下看。
03
PART
輸出到文件
那現(xiàn)在我們的新庫有了數(shù)據(jù),我們就可以切換到單庫模式來從新庫里獲取數(shù)據(jù),然后生成到tsv文件里
[HttpGet]public async Task<MessageModel<string>> SaveData2TsvAsync(){var data = new MessageModel<string>() { success = true, msg = "" };if (_env.IsDevelopment()){try{// 取出數(shù)據(jù),序列化,自己可以處理判空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 = "當(dāng)前不處于開發(fā)模式,代碼生成不可用!";}return data;}
結(jié)果我就不展示了,自己試試就可以了。
思考與總結(jié)
從上邊的代碼中,我們可以看出來,因?yàn)榭蚣芤呀?jīng)集成了很多重要的功能,比如讀寫分離和事務(wù)處理,所以代碼還是比較簡單的,如果自己從0開始寫,還是比較麻煩的。
現(xiàn)在還有一個(gè)問題需要思考下,如果實(shí)現(xiàn)不同類型數(shù)據(jù)庫的生成,這里也是兩種辦法:
1、使用框架的多庫模式,先從庫1獲取數(shù)據(jù),然后切換數(shù)據(jù)庫,再生成到庫2;
2、可以生成到tsv文件里做個(gè)跳板,這不過這里有一個(gè)問題,就是關(guān)系表的id如果不一樣,一定會(huì)混亂的,所以這個(gè)時(shí)候又說到了主鍵用INT還是GUID的問題了,自己處理吧。
還是歡迎大家多多提意見吧,如何對(duì)業(yè)務(wù)數(shù)據(jù)進(jìn)行同步遷移,是一個(gè)好課題。
總結(jié)
以上是生活随笔為你收集整理的实现业务数据的同步迁移 · 思路一的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。