如何动态关联ORM并推断DTO实现灵活查询?

摘要:如果事先没有定义静态关系,在实际代码中,我们就需要提供一种使用动态关系的机制,让我们的查询、类型推断、DTO推断等能力得以正常使用。
在上一篇文章(Prisma不能优雅的支持DTO,试试Vona ORM吧)中,我们基于静态关系实现了目录树的关联查询,并且动态推断生成了DTO(用于Swagger元数据)。在这篇文章我们探讨动态关系的用法。 什么是动态关系 那么,什么是动态关系呢?在大型业务系统中,我们会创建大量数据模型,这些数据模型之间的关联众多,我们不可能将所有关联通过静态关系的机制事先声明出来。特别是当存在大量业务模块,这些数据模型散落在不同的业务模块中,那么通过静态关系事先声明所有的关联关系也变得不太现实。比如,Prisma就只支持静态关系。如果事先没有定义静态关系,在实际代码中,我们就需要提供一种使用动态关系的机制,让我们的查询、类型推断、DTO推断等能力得以正常使用。 准备数据模型 在上一篇文章中,我们已经介绍了如何创建Entity和Model,这里不再赘述。而是直接把Order和Customer的Entity和Model罗列出来,然后演示动态关系的用法 Entity 1. Order @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()) customerId: TableIdentity; } v.openapi:声明字段的元数据,用于Swagger title:采用$locale定义,从而让Swagger元数据支持多语言能力 v.title:等价于v.openapi({ title: ... }) TableIdentity:string | number 2. Customer @Entity() export class EntityCustomer extends EntityBase { @Api.field(v.min(3)) name: string; } Model 1. Order @Model({ entity: EntityOrder }) export class ModelOrder extends BeanModelBase {} 2. Customer @Model({ entity: EntityCustomer }) export class ModelCustomer extends BeanModelBase {} 基于动态关系的查询 现在我们查询订单列表,包含归属的顾客信息: const orders = await this.scope.model.order.select({ with: { customer: $relationDynamic.belongsTo( () => ModelOrder, () => ModelCustomer, 'customerId', ), }, }); $relationDynamic:提供一组工具,用于定义动态关系 belongsTo:定义多对一的关系 参数1:Order模型 参数2:Customer模型 参数3:设置关联外键customerId 下面我们看一下orders的类型推断效果: 自动推断DTO 现在我们自动推断DTO,并且设为API的返回数据的类型: const DtoOrderResult = $Dto.get( () => ModelOrder, { with: { customer: $relationDynamic.belongsTo( () => ModelOrder, () => ModelCustomer, 'customerId', ), }, }, ); @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的代码骨架: @Dto() export class DtoOrderResult {} 然后我们通过继承机制来封装DTO: const DtoOrderResultDynamic = $Dto.get( () => ModelOrder, { with: { customer: $relationDynamic.belongsTo( () => ModelOrder, () => ModelCustomer, 'customerId', ), }, }, ); @Dto() export class DtoOrderResult extends DtoOrderResultDynamic {} 现在,我们再使用新创建的DTO来改造前面的API代码: + 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已开源:https://github.com/vonajs/vona。 Vonajs作者正在B站直播撰写技术文档,工作日每晚8:30,欢迎围观:濮水代码直播间