如何基于动态关系进行ORM关联查询,并动态推断DTO?

在上一篇文章(Prisma不能优雅的支持DTO,试试Vona ORM吧)中,我们基于静态关系实现了目录树的关联查询,并且动态推断生成了DTO(用于Swagger元数据)。在这篇文章我们探讨动态关系的用法。

什么是动态关系

那么,什么是动态关系呢?在大型业务系统中,我们会创建大量数据模型,这些数据模型之间的关联众多,我们不可能将所有关联通过静态关系的机制事先声明出来。特别是当存在大量业务模块,这些数据模型散落在不同的业务模块中,那么通过静态关系事先声明所有的关联关系也变得不太现实。比如,Prisma就只支持静态关系。如果事先没有定义静态关系,在实际代码中,我们就需要提供一种使用动态关系的机制,让我们的查询、类型推断、DTO推断等能力得以正常使用。

准备数据模型

在上一篇文章中,我们已经介绍了如何创建Entity和Model,这里不再赘述。而是直接把Order和Customer的Entity和Model罗列出来,然后演示动态关系的用法

Entity

1. Order

typescript 复制代码
@Entity()
export class EntityOrder extends EntityBase {
  @Api.field(v.openapi({ title: $locale('OrderNo') }), v.default(''), v.min(3))
  orderNo: string;

  @Api.field(v.title($locale('Remark')), v.optional())
  remark?: string;

  @Api.field(v.tableIdentity())
  custumerId: TableIdentity;
}
  • v.openapi:声明字段的元数据,用于Swagger
    • title:采用$locale定义,从而让Swagger元数据支持多语言能力
  • v.title:等价于v.openapi({ title: ... })
  • TableIdentity:string | number

2. Customer

typescript 复制代码
@Entity()
export class EntityCustomer extends EntityBase {
  @Api.field(v.min(3))
  name: string;
}

Model

1. Order

typescript 复制代码
@Model({ entity: EntityOrder })
export class ModelOrder extends BeanModelBase {}

2. Customer

typescript 复制代码
@Model({ entity: EntityCustomer })
export class ModelCustomer extends BeanModelBase {}

基于动态关系的查询

现在我们查询订单列表,包含归属的顾客信息:

typescript 复制代码
const orders = await this.scope.model.order.select({
  with: {
    customer: $relationDynamic.belongsTo(
      () => ModelOrder,
      () => ModelCustomer,
      'custumerId',
    ),
  },
});
  • $relationDynamic:提供一组工具,用于定义动态关系
  • belongsTo:定义多对一的关系
    • 参数1:Order模型
    • 参数2:Customer模型
    • 参数3:设置关联外键custumerId

下面我们看一下orders的类型推断效果:

自动推断DTO

现在我们自动推断DTO,并且设为API的返回数据的类型:

typescript 复制代码
const DtoOrderResult = $Dto.get(
  () => ModelOrder,
  {
    with: {
      customer: $relationDynamic.belongsTo(
        () => ModelOrder,
        () => ModelCustomer,
        'custumerId',
      ),
    },
  },
);

@Controller('order')
export class ControllerOrder extends BeanBase {
  @Web.get()
  @Api.body(v.array(v.object(DtoOrderResult)))
  async findAll() {
    return await this.scope.service.order.findAll();
  }
}  
  • 行1:动态创建DtoOrderResult
  • 行17:将DtoOrderResult用于Swagger/Openapi的元数据

下面我们看一下API的Swagger效果:

封装DTO

我们还可以创建一个新的DTO,将前面动态创建的DtoOrderResult封装起来,从而用于其他地方:

在VSCode中,可以通过右键菜单Vona Create/Dto创建DTO的代码骨架:

typescript 复制代码
@Dto()
export class DtoOrderResult {}

然后我们通过继承机制来封装DTO:

typescript 复制代码
const DtoOrderResultDynamic = $Dto.get(
  () => ModelOrder,
  {
    with: {
      customer: $relationDynamic.belongsTo(
        () => ModelOrder,
        () => ModelCustomer,
        'custumerId',
      ),
    },
  },
);

@Dto()
export class DtoOrderResult extends DtoOrderResultDynamic {}

现在,我们再使用新创建的DTO来改造前面的API代码:

diff 复制代码
+ import { DtoOrderResult } from '../dto/orderResult.ts';

@Controller('order')
export class ControllerOrder extends BeanBase {
  @Web.get()
+ @Api.body(v.array(v.object(DtoOrderResult)))
+ async findAll(): Promise<DtoOrderResult[]> {
    return await this.scope.service.order.findAll();
  }
}
  • 行6: 直接传入DtoOrderResult
  • 行7: 返回类型为Promise<DtoOrderResult[]>

结语

Vonajs已开源:github.com/vonajs/vona

Vonajs作者正在B站直播撰写技术文档,工作日每晚8:30,欢迎围观:濮水代码直播间

相关推荐
FungLeo3 小时前
成为全栈·Node 后端篇·辅助接口:相邻、相关、目录、统计与搜索
node.js·成为全栈
抓不住时间的沙8 小时前
Butterfly主题 5.7 导航栏添加相册同时设置相册入口密码
css·python·node.js
FungLeo9 小时前
成为全栈·Node 后端篇·通知系统:事件消费与已读态管理
node.js·成为全栈·通知系统·事件消费·已读状态管理
晚安日记wanna10 小时前
TS const 类型参数一道面试题的四层追问
前端·面试·typescript
掰头战士10 小时前
你说你折这玩意干什么-agent工作的核心,还真得好好考虑怎么折叠上下文
typescript·llm·agent
ynchyong10 小时前
VUE 中 不能将类型“R[]”分配给类型“UnwrapRefSimple<R>[]”
typescript·vue·ts·unwraprefsimple
掰头战士12 小时前
让散乱的工具调用成为正规军,今天咱们聊聊 Tool System管线的读写锁
typescript·llm·agent
半糖程序员16 小时前
从零构建 Agent(5):让事件流同时提供过程和结果
typescript·agent
西瓜太郎49916 小时前
API Key 轮换不该靠“瞬间替换”:用双 Key 灰度避免线上中断
node.js·api