Sequelize6关联查询为何需加dataValues才能取别名属性
Sequelize 6.x 嵌套关联查询别名需通过dataValues访问的原因
复现场景:Sequelize 6.x 中执行三层关联查询,关联链路为
ForTrade belongsTo Artwork belongsTo Viewer,查询时对Viewer模型的wallet_address字段设置别名trader_address,直接通过实例属性路径访问别名返回undefined,必须追加.dataValues才能获取正确值。
关联查询代码参考:
let _bid = await ForTrade.findOne({ where: {id:_id}, include:[{ model:Artwork, include: [{ model:Viewer, attributes:[["wallet_address", "trader_address"]] }] }] }); // 直接访问返回undefined console.log(_bid.artwork.viewer.trader_address); // 追加dataValues可以正常取值 console.log(_bid.artwork.viewer.dataValues.trader_address);
关联定义代码参考:
Artwork.belongsTo(Viewer, {foreignKey: 'uploader_id'}); ForTrade.belongsTo(Artwork, {foreignKey: "artwork_id"});
核心原因
Sequelize的Model实例对实例.属性名这种直接访问方式,是基于模型初始化时预定义的字段做的代理映射,只有提前在模型的attributes配置中显式声明的字段,才会被挂载到实例顶层供直接访问。
上述场景中的trader_address是查询阶段临时定义的字段别名,从来没有在Viewer模型的定义中注册过,因此不会被Sequelize的属性代理逻辑识别,直接访问自然返回undefined。
为什么dataValues可以正常取值
dataValues是Sequelize实例上专门存储数据库原始查询结果的内部对象,所有从数据库返回的字段——不管是模型预定义的字段、查询时临时生成的别名、SQL层面的计算字段——都会被完整存入这个对象,不会经过模型属性规则的过滤,因此可以直接取到临时别名对应的值。
更规范的访问方案
不推荐在业务代码中长期直接读取dataValues,可以用两种更稳妥的方式实现无dataValues访问:
- 转普通对象后访问
查询完成后调用实例的get({plain: true})方法,把Sequelize实例转为普通JavaScript对象,所有字段(包括临时别名)都会直接挂在对象顶层:const _bidPlain = _bid.get({plain: true}); // 可直接访问,无需dataValues console.log(_bidPlain.artwork.viewer.trader_address); - 模型层预定义虚拟字段
如果trader_address是业务中高频使用的别名,可以在Viewer模型定义时新增一个VIRTUAL类型的属性,配置getter映射到wallet_address字段,后续所有查询场景都可以直接通过实例属性访问。
注意事项
Sequelize实例的顶层属性和dataValues并不是完全同步的:
- 模型预定义字段、通过实例setter方法合法赋值的字段,会在顶层属性和dataValues之间同步更新
- 查询临时别名、原生SQL返回的未声明字段、手动写入dataValues的自定义值,不会自动代理到实例顶层,只能通过dataValues或者转plain对象后访问。
内容的提问来源于stack exchange,提问作者user938363
相关产品推荐
相关产品推荐

