首页
赞助博主
友链
关于
随机CG图
推荐
我的B站主页
我的歌单
我的bgm
井字棋
待办事项
github加速
Search
1
iOS永久不续签随意装软件,trollstore巨魔商店安装教程
3,443 阅读
2
进入自己原神服务器
1,755 阅读
3
linux云服开原神服务器
1,615 阅读
4
从零开始的mc联机教程
1,126 阅读
5
win上开原神服务器
1,110 阅读
默认分类
原神
MC
iOS
galgame
ReinaManager
学习笔记
开发笔记
登录
Search
标签搜索
reinamanager
原神
私服
win
rust
seaorm
安卓
tauri
react
mui
react router
tauri-plugin-sql
migration
sea-orm-cli
基线迁移
数据库迁移
dto
repository
sql
火神80
累计撰写
13
篇文章
累计收到
14
条评论
首页
栏目
默认分类
原神
MC
iOS
galgame
ReinaManager
学习笔记
开发笔记
页面
赞助博主
友链
关于
随机CG图
推荐
我的B站主页
我的歌单
我的bgm
井字棋
待办事项
github加速
搜索到
1
篇与
的结果
2025-10-21
构建数据库repository层——从tauri-plugin-sql重构到seaorm #2
使用dto灵活转换数据结构在构建数据库的repository层时,灵活的数据结构转换是非常重要的。通过使用DTO(数据传输对象),可以在前后端之间传递数据时,保持数据结构的清晰和一致性。entity实体结构用于数据库操作,而DTO则用于与前端交互。对于前端需要插入游戏而传递的数据,不存在后端需要的自增主键id,entity结构中包含id字段,而且要求不为空,如果直接使用entity结构进行插入操作,则需要前端添加这个并无意义的id字段。为此我准备了一个GameInsertDto结构,只包含前端传递的必要字段:/// 用于插入游戏的数据结构(不包含 id, created_at, updated_at) #[derive(Clone, Debug, Serialize, Deserialize)] pub struct InsertGameData { pub bgm_id: Option<String>, pub vndb_id: Option<String>, pub id_type: String, pub date: Option<String>, pub localpath: Option<String>, pub savepath: Option<String>, pub autosave: Option<i32>, pub clear: Option<i32>, pub custom_name: Option<String>, pub custom_cover: Option<String>, }普通的dto结构体无法直接用于seaorm的数据库操作,因此需要实现一个转换方法:/// Trait:将 DTO 转换为 ActiveModel pub trait IntoActiveModel<T> { fn into_active_model(self, game_id: i32) -> T; } impl IntoActiveModel<bgm_data::ActiveModel> for BgmDataInput { fn into_active_model(self, game_id: i32) -> bgm_data::ActiveModel { bgm_data::ActiveModel { game_id: Set(game_id), image: Set(self.image), name: Set(self.name), name_cn: Set(self.name_cn), aliases: Set(self.aliases), summary: Set(self.summary), tags: Set(self.tags), rank: Set(self.rank), score: Set(self.score), developer: Set(self.developer), } } }更新数据需要删除某个列时(设为null),正常情况下的反序列化会将缺失的字段和显式的null值都解析为None,导致无法区分这两种情况。为了解决这个问题,可以自定义一个double_option函数,用于分辨从前端传来的未定义字段和显式的null值:fn double_option<'de, D, T>(deserializer: D) -> Result<Option<Option<T>>, D::Error> where D: Deserializer<'de>, T: Deserialize<'de>, { Ok(Some(Option::deserialize(deserializer)?)) } #[serde(default, deserialize_with = "double_option")] pub bgm_id: Option<Option<String>> // 各行数据......构建repository层重构到seaorm的优势将在这里体现出来:repository层中大部分的数据库操作逻辑,不再需要使用sql语句,而是通过orm方法进行增删改查;因为需要将原来的总games表进行拆分,有关数据库的操作从简单的一对一映射,变成了一对多映射,而seaorm可以轻松定义实体间的关系(如 One-to-One, One-to-Many等),使一对多映射的实现变得简单。插入示例:/// 批量插入游戏数据(包含关联数据) pub async fn insert_with_related( db: &DatabaseConnection, game: InsertGameData, bgm: Option<BgmDataInput>, vndb: Option<VndbDataInput>, other: Option<OtherDataInput>, ) -> Result<i32, DbErr> { let txn = db.begin().await?; // 构建 ActiveModel 并插入游戏基础数据 let now = chrono::Utc::now().timestamp() as i32; let game_active = games::ActiveModel { id: NotSet, bgm_id: Set(game.bgm_id), vndb_id: Set(game.vndb_id), id_type: Set(game.id_type), date: Set(game.date), localpath: Set(game.localpath), savepath: Set(game.savepath), autosave: Set(game.autosave), clear: Set(game.clear), custom_name: Set(game.custom_name), custom_cover: Set(game.custom_cover), created_at: Set(Some(now)), updated_at: Set(Some(now)), }; let game_model = game_active.insert(&txn).await?; let game_id = game_model.id; // 使用辅助函数插入关联数据 Self::insert_bgm_data(&txn, game_id, bgm).await?; Self::insert_vndb_data(&txn, game_id, vndb).await?; Self::insert_other_data(&txn, game_id, other).await?; txn.commit().await?; Ok(game_id) }查询示例:/// 根据 ID 查询完整游戏数据(包含关联数据) pub async fn find_full_by_id( db: &DatabaseConnection, id: i32, ) -> Result<Option<FullGameData>, DbErr> { let game = match Games::find_by_id(id).one(db).await? { Some(g) => g, None => return Ok(None), }; let bgm = BgmData::find_by_id(id).one(db).await?; let vndb = VndbData::find_by_id(id).one(db).await?; let other = OtherData::find_by_id(id).one(db).await?; Ok(Some(FullGameData { game, bgm_data: bgm, vndb_data: vndb, other_data: other, })) }这篇文章没图,那就放个野生的里想奈/滑稽
2025年10月21日
4 阅读
0 评论
1 点赞