新闻详情

新闻详情

首页 / 资讯中心 / 详情

mikro-orm 虚拟实体(Virtual Entities)实战:用动态 SQL 与 MongoDB 聚合映射只读实体

发布时间:2026/9/25 5:12:56来源:尧图网络
mikro-orm 虚拟实体(Virtual Entities)实战:用动态 SQL 与 MongoDB 聚合映射只读实体
后端【免费下载链接】mikro-ormTypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, MariaDB, MS SQL Server, PostgreSQL and SQLite/libSQL databases.项目地址https://gitcode.com/gh_mirrors/mi/mikro-orm点击查看免费下载本文基于 mikro-orm 官方文档virtual-entities章节以 version-6.6 文档为基准结合仓库中的驱动源码与测试用例完整讲解虚拟实体的定义方式、字符串 SQL 表达式与回调表达式的区别、MongoDB 聚合用法以及查询时 ORM 底层的执行机制。读完之后你可以掌握如何把任意 SQL 查询或聚合管道的结果直接映射为可查询的实体并理解find/count/stream/qb在虚拟实体上的真实执行路径。一、什么是虚拟实体虚拟实体Virtual Entities不对应任何数据库表。相反它们在查询时动态解析为一条 SQL 查询在 MongoDB 中是一条聚合管道从而允许把任意形式的查询结果映射到实体上。这类实体面向只读场景它们没有主键因此无法被 Unit of Work 追踪变更不能persist/remove/更新。在某种意义上它们类似于数据库视图——你可以用它来代理已经存在的原生视图。两条关键约束来自文档的醒目提示虚拟实体可以包含标量属性以及单值关系M:1 和 1:1 的所有者侧。这类关系始终通过select-in策略填充表达式中的列名必须遵循当前命名策略。文档示例中authorName属性对应的列名就是author_name。与后续版本的关系6.6 文档中写明数据库视图目前不受支持虚拟实体是当时代理原生视图的替代方案当前仓库中新增了独立的 View Entities 章节见 view-entitiesview 实体由 Schema Generator 生成CREATE VIEW语句并纳入迁移追踪而虚拟实体在查询时才评估表达式——两者定位不同可按需选用。从源码结构看一个实体是否被标记为虚拟由元数据推导在 EntityMetadata 中存在this.virtual !!this.expression !this.view的判断即定义了expression且不是 view 实体就自动视为虚拟实体。二、用字符串 SQL 定义虚拟实体最直接的写法给实体元数据提供expression其值可以是一个 SQL 字符串。以四种实体定义风格分别给出与原文档一致的./entities/BookWithAuthor.ts示例2.1 reflect-metadata / ts-morph 装饰器风格Entity({ expression: select b.title, a.name as author_name, ( select group_concat(distinct t.name) from book b join tags_ordered bt on bt.book_id b.id join book_tag t on t.id bt.book_tag_id where b.author_id a.id group by b.author_id ) as tags from author a group by a.id }) export class BookWithAuthor { Property() title!: string; Property() authorName!: string; Property() tags!: string[]; }注意authorName属性通过 SQL 别名author_name映射遵循默认的 snake_case 命名策略tags列由关联子查询聚合而成。2.2 defineEntity 风格import { type InferEntity, defineEntity } from mikro-orm/core; export const BookWithAuthor defineEntity({ name: BookWithAuthor, expression: select b.title, a.name as author_name, ( select group_concat(distinct t.name) from book b join tags_ordered bt on bt.book_id b.id join book_tag t on t.id bt.book_tag_id where b.author_id a.id group by b.author_id ) as tags from author a group by a.id , properties: p ({ title: p.string(), authorName: p.string(), tags: p.type(string[]).$typestring[](), }), }); export interface IBookWithAuthor extends InferEntitytypeof BookWithAuthor {}2.3 EntitySchema 风格export interface IBookWithAuthor { title: string; authorName: string; tags: string[]; } export const BookWithAuthor new EntitySchemaIBookWithAuthor({ name: BookWithAuthor, expression: select b.title, a.name as author_name, ( select group_concat(distinct t.name) from book b join tags_ordered bt on bt.book_id b.id join book_tag t on t.id bt.book_tag_id where b.author_id a.id group by b.author_id ) as tags from author a group by a.id , properties: { title: { type: string }, authorName: { type: string }, tags: { type: string[] }, }, });文档中 reflect-metadata 与 ts-morph 两个标签页的示例代码完全相同仅编译链/元数据解析方式不同上面 2.1 已统一给出。三、用回调定义虚拟实体Query Builder 写法第二种方式是提供一个回调函数返回 Query Builder或字符串、raw片段。回调接收(em, where, options)三个参数Entity({ expression: (em: EntityManager) { return em.createQueryBuilder(Book, b) .select([b.title, a.name as author_name, group_concat(t.name) as tags]) .join(b.author, a) .join(b.tags, t) .groupBy(b.id); }, }) export class BookWithAuthor { Property() title!: string; Property() authorName!: string; Property() tags!: string[]; }defineEntity风格的等价写法export const BookWithAuthor defineEntity({ name: BookWithAuthor, expression: (em: EntityManager) { return em.createQueryBuilder(Book, b) .select([b.title, a.name as author_name, group_concat(t.name) as tags]) .join(b.author, a) .join(b.tags, t) .groupBy(b.id); }, properties: p ({ title: p.string(), authorName: p.string(), tags: p.type(string[]).$typestring[](), }), });EntitySchema风格export const BookWithAuthor new EntitySchemaIBookWithAuthor({ name: BookWithAuthor, expression: (em: EntityManager) { return em.createQueryBuilder(Book, b) .select([b.title, a.name as author_name, group_concat(t.name) as tags]) .join(b.author, a) .join(b.tags, t) .groupBy(b.id); }, properties: { title: { type: string }, authorName: { type: string }, tags: { type: string[] }, }, });expression 的类型签名在 EntityMetadata 类型定义 中expression的完整签名是expression?: | string | (( em: any, where: ObjectQueryEntity, options: FindOptionsEntity, any, any, any, stream?: boolean, ) MaybePromiseRaw | object | string);从源码结构看回调的返回值可以是字符串、Raw片段、Query Builder 实例或 POJO 数组且回调本身可以是async返回MaybePromise。第四个参数stream?: boolean用于区分当前是流式查询还是普通查询回调可以据此选择返回数据库游标或普通数组。四、MongoDB用聚合管道定义虚拟实体在 MongoDB 上expression回调通常返回一条聚合管道。文档给出的示例是前面 SQL 示例的粗略等价物Entity({ expression: (em: EntityManager, where, options) { const $sort { ...options.orderBy } as Dictionary; $sort._id 1; const pipeline: Dictionary[] [ { $project: { _id: 0, title: 1, author: 1 } }, { $sort }, { $match: where ?? {} }, { $lookup: { from: author, localField: author, foreignField: _id, as: author, pipeline: [{ $project: { name: 1 } }] } }, { $unwind: $author }, { $set: { authorName: $author.name } }, { $unset: [author] }, ]; if (options.offset ! null) { pipeline.push({ $skip: options.offset }); } if (options.limit ! null) { pipeline.push({ $limit: options.limit }); } return em.aggregate(Book, pipeline); }, }) export class BookWithAuthor { Property() title!: string; Property() authorName!: string; }要点文档明确提示由于聚合管道的特性这种写法并不十分人体工学ergonomicwhere查询条件以及orderBy、limit、offset等选项必须在你的管道中显式处理——示例中通过$match: where ?? {}、$sort、$skip/$limit完成回调最终返回em.aggregate(Book, pipeline)由驱动等待其结果并映射为实体数据。驱动侧的实现印证了这一点MongoDriver.findVirtual 中若meta.expression是函数则创建一个新的EntityManager并调用meta.expression(em, where, options)直接把回调的返回值当作EntityData[]使用streamVirtual 则额外传入true作为第四个参数表示流式场景。五、SQL 驱动底层执行机制理解虚拟实体在 SQL 数据库上的行为需要看 AbstractSqlDriver 的实现。这里区分两种expression形态5.1 字符串表达式外层子查询包装当expression是字符串时findFromVirtual 会调用wrapVirtualExpressionInSubquery把原始 SQL 包进一个派生表(${expression}) as qb_alias由一个普通 QueryBuilder 负责在外层应用where、orderBy、limit、offset。关键源码const asKeyword this.platform.usesAsKeyword() ? as : ; native.from(raw((${expression})${asKeyword}${this.platform.quoteIdentifier(qb.alias)}));这带来几个可验证的行为过滤与排序是外层的em.find(BookWithAuthor, { authorName: ... }, { orderBy, limit, offset })实际执行的是select ... from (你的完整SQL) as qb where qb.author_name ? order by ... limit ...因此你只能按表达式结果集中可见的列做过滤/排序count 的实现countVirtual 走同样的路径但会clear(select).clear(limit).clear(offset).count()即对子查询结果直接countstream 支持wrapVirtualExpressionInSubqueryStream 用同一套子查询包装逻辑但走连接的stream接口逐行产出实体适合大结果集若处于行级安全RLS会话上下文中且非显式事务上下文驱动会为其套一层短事务来执行查询源码注释说明了这一意图。5.2 回调表达式fork EM 多形态返回值当expression是函数时见 findFromVirtual 后半段驱动会fork调用方 EM若 options 中带em使回调继承其过滤器、过滤参数与会话上下文并通过em.setTransactionContext(options.ctx)保持事务一致性回调的返回值按形态分派返回字符串→ 走与字符串表达式相同的子查询包装返回QueryBuilder→ 取其getFormattedQuery()再包装返回Raw 片段→ 先用platform.formatQuery格式化再包装返回POJO 数组如 MongoDB 场景→ 直接作为结果返回不再包装。六、测试用例中的行为验证仓库中有一组针对虚拟实体的专项测试可作为行为依据SQLitevirtual-entities.sqlite.test.tsPostgreSQLvirtual-entities.postgres.test.tsMongoDBvirtual-entities.mongo.test.ts回调中 filters 的传递virtual-entity-callback-filters.sqlite.test.ts相关快照snapshots含 postgres 与 sqlite 两份快照以 SQLite 测试为例它覆盖了一个AuthorProfile实体字符串 SQL、() raw(sql)、() sql、Query Builder 回调四种表达形式并验证了以下能力findAndCount 结果缓存orm.em.findAndCount(AuthorProfile, {}, { cache: 5000 })返回[profiles, 3]且带缓存的第二次调用不再产生新的 SQLmock 日志调用次数保持为 4——说明虚拟实体同样支持cache选项streamorm.em.stream(AuthorProfile, { orderBy: { name: 1, usedTags: 1 } })以异步迭代方式产出实体且结果与find一致QueryBuilder 分页orm.em.qb(AuthorProfile).limit(2).offset(1).orderBy({ name: asc }).getResult()精确得到[Jon Snow 2, Jon Snow 3]印证了外层子查询 外层 limit/offset的执行模型where 操作符{ $and: [{ name: { $like: Jon% } }, { age: { $gte: 0 } }] }这类复合过滤直接作用于子查询结果列内嵌属性Embeddable映射表达式结果中的列被映射到identity: { foo, bar }对象属性序列化时按实体属性输出。另外GH4628.test.ts 针对社区反馈的具体问题做了回归测试说明虚拟实体的边界情况如与 QueryBuilder 交互在持续被覆盖。七、使用建议与限制小结定位虚拟实体只适合读模型报表、聚合视图、跨表联查投影不能写入不需要也无法定义主键命名策略SQL 别名必须与命名策略后的列名一致authorName↔author_name否则会映射失败where/orderBy 的作用面字符串表达式场景下过滤、排序、分页都作用于表达式结果集的外层因此表达式必须输出你想要过滤/排序的列关系填充实体上的 M:1 / 1:1 关系一律走select-in策略MongoDB 差异管道需要自行处理$match/$sort/$skip/$limit且聚合写法灵活性不如 SQL 直观需要真正的数据库视图建表 DDL 纳入迁移管理时参考 View Entities虚拟实体则适合查询时求值的轻量映射例如代理已有的原生视图或复杂的聚合查询。以上行为均以 version-6.6 文档为基准源码引用来自当前仓库核心类型定义、mikro-orm/sql抽象 SQL 驱动、mikro-orm/mongodb驱动与特性测试在 6.6 及以上版本中该机制的核心执行路径保持一致。赞分享后端【免费下载链接】mikro-ormTypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, MariaDB, MS SQL Server, PostgreSQL and SQLite/libSQL databases.项目地址https://gitcode.com/gh_mirrors/mi/mikro-orm点击查看免费下载相关推荐深入解析MegaBasterd跨平台MEGA客户端的技术架构与实现原理深入解析MegaBasterd跨平台MEGA客户端的技术架构与实现原理 MegaBasterd作为一款功能丰富的跨平台MEGA下载器、上传器和流媒体套件其技桌面应用Doctrine ORM Native SQL 完全指南用 NativeQuery 与 ResultSetMapping 执行原生 SQL 并映射为实体Doctrine ORM Native SQL 完全指南用 NativeQuery 与 ResultSetMapping 执行原生 SQL 并映射为实体 Na数据库ORM后端Prism-Samples-Wpf导航系统详解从基础导航到高级路由控制Prism Samples Wpf导航系统详解从基础导航到高级路由控制 Prism Samples Wpf导航系统是构建现代化WPF应用程序的核心组件它提供后端上一篇探索Nano IDRust中的轻量级唯一ID生成器下一篇SwiftUI布局系统深度解析从FlowLayout到自定义Layout协议的完整指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Atlas 300V 24G跑YOLO全流程:从环境搭建到推理优化 2026/9/25 5:40:09

Atlas 300V 24G跑YOLO全流程:从环境搭建到推理优化

干了这么多年AI部署,说实话被各种推理卡折磨过不少回,Atlas 300V 24G 这张卡算是让我印象比较深的一张。一开始单纯以为它就是一张普通的 PCIe 加速卡,结果从驱动到算子适配到模型转换,每一步都有它自己的脾气。这篇文章就围绕 At…

阅读更多 →
Atlas 300V 24G推理加速卡部署YOLOv5全流程实战 2026/9/25 5:40:09

Atlas 300V 24G推理加速卡部署YOLOv5全流程实战

最近在折腾Atlas这块卡,把YOLO模型从PyTorch一路迁移到昇腾推理环境,踩了不少坑,也终于理清了整套流程。先说结论:Atlas 300V 24G确实是运算加速卡,但更准确的说法是AI推理加速卡,它和打游戏的显卡、跑训练…

阅读更多 →
CIFAR-10/CIFAR-100稳定下载与数据验证指南 2026/9/25 5:40:03

CIFAR-10/CIFAR-100稳定下载与数据验证指南

1. 项目概述:为什么CIFAR-10和CIFAR-100仍是深度学习入门绕不开的“第一块砖”你刚打开PyTorch文档,想跑通第一个图像分类模型,官方教程里赫然写着torchvision.datasets.CIFAR10;你在Keras官网上找示例代码,tf.keras.d…

阅读更多 →
Atlas 300V Pro 24G推理卡部署YOLO实战:从环境搭建到性能调优 2026/9/25 5:39:57

Atlas 300V Pro 24G推理卡部署YOLO实战:从环境搭建到性能调优

1. 一张24GB的推理卡,先搞清楚它能干什么先回答那个被反复问到的问题:Atlas 300V 24G确实是运算加速卡,而且不是那种插在个人电脑里跑游戏的显卡,它的定位是数据中心和边缘服务器里的AI推理加速卡。很多朋友看到“300V”“24G”第…

阅读更多 →
Type 1 Hypervisor:车规功能安全的确定性执行基座 2026/9/25 5:39:57

Type 1 Hypervisor:车规功能安全的确定性执行基座

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
华为Atlas 300V 24G部署YOLO:从ONNX到OM完整实战指南 2026/9/25 5:39:57

华为Atlas 300V 24G部署YOLO:从ONNX到OM完整实战指南

1. 项目概述:Atlas到底是什么东西?很多人第一次听到Atlas,要么以为它是某张游戏显卡,要么以为是某个开源项目代号。实际上,华为Atlas是昇腾AI计算平台的产品线,本质上是一张专门用来跑神经网络推理的运算加…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉