批量创建

Odoo 批量 create 为什么不只是少写一个 for:@api.model_create_multi 的真正价值

很多人知道 create 能收 list,却没真正理解 @api.model_create_multi 在 Odoo 里解决的是“批量入口一致性”和“批处理副作用”问题。本文从官方源码和常见重写误区讲透。

Odoo 开发
进阶 开发者 2 分钟阅读
0 评论 0 点赞 0 收藏 6 阅读

先说结论

@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 时,先问自己三个问题:

  1. 我处理的是每条自己的逻辑,还是整批共享逻辑
  2. 有没有东西本来可以先批量查一次
  3. 这个副作用应该按记录触发,还是按整批触发

只要第 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

评论区

想参与讨论?先 登录 再发表评论。
还没有评论,你可以成为第一个留言的人。