先说结论
@api.model_create_multi 的重点,不是“让你少写一个 for”。
它真正解决的是两件事:
- 让
create()的输入统一成vals_list - 让你有机会把本来会重复执行的逻辑,提升到批处理层做一次
官方源码在 odoo/orm/decorators.py 里已经把意图写得很直接:
- 调用方既可以传一个
dict - 也可以传一个
list[dict] - 装饰器会把单个
dict包成列表,再交给你的方法
所以它本质上不是“新功能”,而是:
给
create()建一个稳定的批量接口约定。
为什么这个点在 Odoo 里特别重要
因为 Odoo 的很多创建动作,本来就不是“一次只建一条”。
比如:
- 导入数据时一批一批创建
- 一张单据落库时顺带创建多条 lines
- 业务逻辑里一次性生成多条 move / move line / activity
- 某些复制、展开、批量操作会连续造记录
如果你的重写只按“单条记录思维”来写,就容易出现两个问题:
1. 能跑,但性能很差
你可能每条都去:
- 查一次配置
- 算一次公共默认值
- 做一次重复校验
- 发一次重复通知
这些操作如果本来可以“整批做一次”,却被你写成“每条做一次”,量一大就很伤。
2. 逻辑副作用会被放大
比如你想在创建后:
- 统一补字段
- 统一做关联
- 统一触发一次后续动作
结果你在循环里每条都做,就容易出现:
- 重复消息
- 重复 SQL
- 重复业务调用
- 顺序依赖被放大
官方装饰器到底做了什么
在 odoo/orm/decorators.py 里,model_create_multi 很朴素:
- 如果传入的是单个
Mapping - 就把它包成
[vals] - 然后把列表交给真正的
create(self, vals_list)
所以它不负责帮你“自动优化业务逻辑”。
它只做一件关键的基础设施工作:
保证你的
create实现始终拿到列表。
这意味着你写重写时,可以很自然地做这些事:
- 先整批扫描输入
- 提前收集所有外键 / key / code
- 批量查一次依赖数据
- 统一改写
vals_list - 最后一次
super().create(vals_list)
这才是它最大的价值。
为什么“create 能收列表”不等于“你的重写就天然安全”
很多人会误以为:
- 反正 Odoo 底层支持列表
- 我的
create里随便按单条写也没事
这就太乐观了。
因为底层支持列表,不代表你自己的 override 还保住了批量语义。
最常见的破坏方式有两种。
写法一:把 vals_list 又拆回单条循环 super()
这会把批量入口重新打散。
问题在于:
- 上游本来可能想批量创建
- 你却在 override 里重新变回 N 次 create
- 于是批处理优化、预取、统一后处理都被你拆没了
写法二:方法签名仍按单个 vals 写
这在有些场景下可能一开始“碰巧能跑”,但一旦遇到真实批量调用,就容易:
- 类型不对
- 修改逻辑失效
- 某些字段默认值补错
- 只处理了第一条或把整批当成一条
所以真正稳的写法应该是:
@api.model_create_multi
def create(self, vals_list):
# 批量预处理
records = super().create(vals_list)
# 批量后处理
return records
什么时候最该用“先整批预处理,再一次 super”
下面这些场景,我会优先按批量思路写。
1. 多条记录共享同一份外部依赖
例如:
- 同一批记录都要查某个配置参数
- 都依赖同一批 partner / product / user
- 都需要从同一个 mapping 表里做转换
这时最合理的做法,是先把依赖一次查全。
2. 你要做跨记录一致性检查
例如:
- 编码不能重复
- 某个组合键不能在同批输入里撞车
- 同一批创建的数据之间要互相协调
单条思维很难把这类问题一次看全。
3. 你想把副作用控制在“批”这个层级
比如:
- 统一记录日志
- 统一发一次通知
- 统一建立后续任务
这类动作通常更适合在 records = super().create(vals_list) 之后,用结果集一次处理。
一个非常实用的判断标准
写 create override 时,先问自己三个问题:
- 我处理的是每条自己的逻辑,还是整批共享逻辑?
- 有没有东西本来可以先批量查一次?
- 这个副作用应该按记录触发,还是按整批触发?
只要第 2、3 题里有一个答案是“整批”,那你就不该把 override 写成纯单条思维。
新手最容易误解的点
误解 1:@api.model_create_multi 只是性能装饰器
不完全对。
它确实有助于性能,但它更底层的价值是:
- 统一接口形状
- 保留批量语义
- 让 override 更容易写对
误解 2:既然是列表,返回值也该是列表
不对。
Odoo create() 返回的仍然是 recordset。
也就是说:
- 输入批量
- 输出也是 ORM 语义下的一组记录
误解 3:只要用了这个装饰器,代码自然就高性能
也不对。
装饰器只给你机会。
你如果内部还是:
- 一条条查库
- 一条条
super - 一条条发消息
那照样会慢。
实战里的推荐心法
我会把它记成一句话:
@api.model_create_multi不是为了让 create“支持列表”,而是为了让你的 override 从一开始就站在“整批记录”视角上思考。
理解这一点后,很多 create 重写会突然变得清爽:
- 前面做批量预处理
- 中间一次
super().create(vals_list) - 后面用 recordset 做批量后处理
这才是 Odoo 官方设计这个装饰器最值得学的地方。
DISCUSSION
评论区