先说结论
Reference 和 Many2oneReference 最容易让人误会的地方,是它们长得像“关系字段”,却不是数据库层面的普通外键关系。
官方源码在 odoo/orm/fields_reference.py 里直接把它们叫做:
Pseudo-relational field(伪关系字段)
这句话非常关键。
因为它意味着:
- 它们确实表达“指向某条记录”
- 但数据库不会像
Many2one那样用标准 FK 帮你兜底
所以它们的价值是灵活,代价是边界更需要开发者自己懂。
为什么 Odoo 需要这种字段
因为有些场景,目标模型不是固定一个。
例如:
- 菜单 action 可能指向 report、window action 等不同模型
- 附件可能挂到任意业务对象
- 某些评分、通用资源、通用引用对象,天然就跨模型
这类需求如果硬塞成普通 Many2one,你会立刻卡住:
Many2one只能固定指向一个 comodel- 但你的目标记录可能来自多个模型
所以 Odoo 需要一种“可变目标模型”的引用机制。
Reference 是怎么存的
源码里写得很清楚:
Reference在数据库里存的是一个字符串- 格式大致是:
"res_model,res_id"
例如可能像:
res.partner,42sale.order,17
所以它把“模型名 + id”塞进一个字段里。
这也是为什么它灵活:
- 模型名是动态的
- id 也跟着模型走
但这也说明它不像普通 FK 那样可以直接由数据库检查“这个 id 一定属于固定表”。
Many2oneReference 又是什么
它和 Reference 很像,但拆法不同。
源码说明它:
- 自己在数据库里存一个整数 id
- 同时要求模型上另有一个
Char字段保存模型名 - 通过
model_field指出“模型名存在哪个字段里”
也就是说,它不是把 model,id 合成一个字符串,而是拆成两列思路:
- 一列存模型名
- 一列存 id
所以它和 Reference 的差别,不在于“是不是能跨模型”,而在于:
跨模型引用信息是合在一个字段里,还是拆到两个字段里。
为什么它们都叫“伪关系”
因为它们都没有普通 Many2one 那种强外键语义。
这会带来几个直接后果。
1. 数据库层保护弱得多
普通 Many2one 的一个重要价值,是数据库知道:
- 这列引用哪个表
- 可以做 FK 约束
但 Reference / Many2oneReference 指向的表本身是动态的,数据库很难给你做同等级保护。
2. 合法性更多依赖 ORM 约定
源码里会做一些校验,比如:
- 模型名是否在允许 selection 里
- 目标记录是否存在
但这些更多是 ORM 层、应用层的保障,不是数据库天然托底。
3. 开发者更要自己想清“删目标记录以后怎么办”
因为关系不是固定 FK,所以 ondelete 语义、脏引用风险、展示逻辑,都比普通 Many2one 更需要主动设计。
它们分别适合什么场景
Reference 更像“一个字段里装完整通用引用”
适合:
- 本身就想把完整引用放进一个字段
- 读写习惯更接近“引用值”
- 界面或逻辑上把它当成通用资源指针
Many2oneReference 更像“模型名 + id 分列存储”
适合:
- 模型名本身也需要单独存在
- 逻辑上会分别使用 model 和 id
- 某些底层关系操作更适合 id 单独存放
它并不是“更强的 Many2one”,只是另一种伪关系表达方式。
新手最容易误解的点
1. 以为它们只是“能指向多个模型的 Many2one”
这会低估风险。
更准确地说,它们是跨模型引用机制,不是普通 FK 的扩展版。
2. 以为数据库会像 Many2one 一样保你干净
不会同等级地保。
3. 以为选哪个只看界面组件
实际上更该看的是:
- 你想怎样存储引用
- 模型名是否要独立存在
- 后续校验和删除边界如何处理
实战设计时我会怎么判断
先问三件事:
- 目标模型是不是天然不固定?
- 这个引用如果失效,系统能不能接受?
- 我更需要“单字段完整引用”,还是“模型名和 id 分离存储”?
如果第 1 题答案是否定的,那大概率还是老老实实用普通 Many2one 更稳。
因为灵活性不是免费的。
一句话记忆法
Reference和Many2oneReference的本质,不是“高级 Many2one”,而是 Odoo 为跨模型引用提供的伪关系字段;它们换来了灵活性,也失去了很多外键级保护。
理解这层,选字段时就不会只看“能不能指过去”,而会开始考虑“指过去以后,数据边界是否还站得住”。
DISCUSSION
评论区