业务实体模型
业务实体模型是基于ER图实现的,目前仅支持实体、关系、枚举三个特性。继承与实体嵌套计划在下一个版本中支持。
该模块主要是一些图形化的拖拽操作,用文字描述这样的操作,是一件枯燥的事情,阅读这样的文字也会相当无聊。
所以,关于如何操作,自己点点试试就好了。或许,出个操作视频是个比较不错的主意,不过要等作者有空的时候。
本文只是概要说一下其中的一些关键点。
#
包一组实体、关系跟ER图的组合,包的概念只是给数据管理提供了方便,对数据库映射没有什么影响。
除了添加、修改删除包以及包内部所包含的实体、关系、ER图等常规操作,包还可以单独发布、导出、导入、导出模型接口类等。
#
发布没有被发布的包,仅仅是数据库中的一条数据记录,不管怎么修改,保存多少次,都不会对系统的运行产生任何影响,更没法通过接口操作它实体的实例数据。
一旦包被发布,意味着它深度融入到我们系统里了,可以通过通用接口增、删、查、改它的数据了。
包的发布不能被撤销,除非您深度了解系统原理,到后端删除相应的发布文件。
这个文件的目录是:\schemas
。一个包对应一个文件,文件名字是包的UUID,删除文件并重启后端,这个包就撤销发布了。但是这个包并没有因此被删除,您还可以再次发布它。
看起来相当麻烦吧?所以,请谨慎操作包的发布。
#
导出JSON把一个整个包的内容,导出成一个JSON文件,并且这个文件还可以被其他人导入并使用。
#
导入包就是把上面说的JSON文件,再次导入。但是重复导入不行,系统会提示错误。
#
导出模型接口类利用IDE对TypeScript的类型识别,能够提高开发效率,减少调试Bug的时间。
导出这些接口类的目的,就是充分利用TypeScript的这个优势。
#
导出文件样板一个Entity,对应一个文件,以RxUser为例,它的代码是这个样子:
import { RxRole } from './RxRole';import { RxMedia } from './RxMedia';import { RxMediaFolder } from './RxMediaFolder';
export const EntityRxUser = 'RxUser';
export interface RxUser { id?: number; name: string; loginName: string; email?: string; isSupper?: boolean; isDemo?: boolean; status: string; createdAt?: Date; updatedAt?: Date; roles?: RxRole[]; medias?: RxMedia[]; mediaFolders?: RxMediaFolder[]; avatar?: RxMedia;}
创建一个新实例的时候,是没有ID的,所以id字段可以为空。其他字段,根据UI界面的设置,决定其是否可空。
如果一个字段被设置为查询时隐藏
,那么这个字段不会出现在导出的接口文件中。
文件中还有一个常量 EntityRxUser
,这个常量可以在操作数据时使用,避免了不容易排错的魔鬼字符串。
#
接口使用样例比如在React中,要查询所有的 RxUser实例,可以这样写代码:
import { useMagicQuery, MagicQueryBuilder } from '@rxdrag/rxmodels-swr';import { RxUser, EntityRxUser } from 'entity-interface/RxUser';
...export function AReactComponent(){ const {data, loading, error} = useMagicQuery<RxUser[]>( new MagicQueryBuilder().setEntity(EntityRxUser) )}...
#
实体实体分为普通实体跟枚举两种,普通实体会映射到数据库,生成一个数据库表格。
枚举更像npm的开发时依赖,只是为了代码可读性更强,对数据库没什么影响,枚举类型的字段映射到数据库,就是一个string类型的字段。
图形界面编辑枚举值目前比较简陋,只能输入JSON字符串,后面的版本应该会优化。
#
隐藏与删除- 删除:工具条上的删除按钮,树形列表里实体节点上的鼠标悬停时也有删除按钮,点击这两处删除,会把实体真的删除掉,包括跟实体有关的的关系也会被一并删除。
- 隐藏:在ER图里,鼠标悬停时,会显示一个隐藏实体的按钮,点击这个按钮只是把这个实体从当前ER图中移除,这个实体还是存在的,并没有被真正删除。
#
字段字段目前支持如下类型:
Number
: 数字Boolean
: 布尔String
: 字符串Date
: 日期SimpleJson
: JSON数据,在接口中表现为 anySimpleArray
: 数组,在接口中表现为any[]Enum
: 枚举
若需要其它类型支持,发issue吧,后续版本加上。
#
关系在数据库层,关系是被映射为外键的,在UI层,把关系放在跟实体并列的地位。
关系也归属与某个包,具体的归属规则是这样的:
- 1对1关系跟多对多关系,通过拥有者指定,关系跟拥有者一个包
- 1对多跟多对1关系中,关系归属于多头方
导出的时候,关系会随所归属的包被导出。
#
1对1假如一个用户只能对应一个 RxMedia 作为头像,一个 RxMedia 只能作为一个用户的头像,那么这就是个一对一的关系。
RxUser 拥有这个关系,那么映射到数据库,在 rx_user
表中会有一个字段 avatarId
对应 RxMedia 的 id。
对应到TypeORM层,相当于在 RxUser 实体的 avatar
字段添加 JoinColumn
。
#
接口代码import { RxUser } from './RxUser';
export const EntityRxMedia = 'RxMedia';
export interface RxMedia { id?: number; ... avatarOfUser?: RxUser; ...}
import { RxMedia } from './RxMedia';
export const EntityRxUser = 'RxUser';
export interface RxUser { ... avatar?: RxMedia; ...}
#
1对多为了演示这个关系,新建了一个包 Blog,新建的实体 Post 跟ER图位于 Blog 包。
1对多关系的拥有者是多方,post 的 author 关系不会影响系统包,可以利用这个特性仔细设计您的包依赖。
#
接口代码import { RxUser } from './RxUser';
export const EntityPost = 'Post';
export interface Post { id?: number; ... author?: RxUser; ...}
如果重新导出系统包,那么RxUser的代码也会被更新,但是这个关系对系统包来说并不重要,您可以不用关注。
#
多对1就是1对多关系的反向,添加这个关系只是为了使用方便,没有什么本质区别。
#
多对多多对多关系中,要留意关系的拥有者是谁,这涉及到包之间的依赖关系。
这个例子中 Blog 包依赖于 System 包,就让Post拥有这个关系。相当于在 TypeORM 代码里, 给 Post的 medias
字段添加了 JoinTable
。
如果项目不大,忽略这些细节,也不会影响您的使用。
#
接口代码export interface Post { id?: number; ... medias?: RxMedia[]; ...}
对于这个例子,同样可以不关注对 RxMedia 接口的影响。
#
多对多关系的附加信息如果真的要做一个 Blog 项目,对 SEO 友好是最基本的要求,这就需要为每一个 Post 关联的图片添加 alt 文本。
记得 larvel 中可以使用 povit 属性,TypeORM并不提供这样的支持。可以再添加一个PostMediaPovit实体,把一个多对多关系,转化成两个1对多关系来解决这个问题。
#
接口代码import { PostMediaPovit } from './PostMediaPovit';import { RxUser } from './RxUser';
export const EntityPost = 'Post';
export interface Post { id?: number; ... author?: RxUser; mediaPovits?: PostMediaPovit[]; ...}
import { Post } from './Post';import { RxMedia } from './RxMedia';
export const EntityPostMediaPovit = 'PostMediaPovit';
export interface PostMediaPovit { id?: number; altText: string; belongsToPost?: Post; media?: RxMedia;}
新建实体跟关系的拥有者还是 Blog 包,这里可以会略对 System 包的影响,不需要重新导出 System 包的接口文件。
通用查询接口支持多级关联查询,用起来同样也很方便。