共享事务的参数位置、静默失效与安全代理

确定性绘制的技术主题封面

摘要| 在 V8 脚本里把事务对象当成表单字段传进去,代码不报错,却会悄悄走另一个事务:写入看似成功,原子性早已破裂。本文用当前源码区域与两项真实执行说明第三个位置参数的作用,以及安全代理为什么不允许脚本自己提交事务。

01|一个看起来更自然、却会静默失效的写法

用 V8 写跨表业务时,共享事务是刚需:主表写入与接口引擎里的从表写入必须在同一个事务里。直觉写法往往是这样——把事务对象塞进表单对象,和字段一起传过去。

const trans = V8.DbTrans;               // 错误:把事务当成表单字段
V8.FormEngine.UptFormData('Order', { Id: 'x', Status: 2, _trans: trans });

这段代码不会报错,甚至能更新成功。但它和调用方的事务没有任何关系:框架开始、提交、结束的是另一个事务。危险之处正在于看起来成功

正确写法只有一种:事务是方法的位置参数,必须放在第三个位置。

V8.FormEngine.UptFormData('Order', { Id: 'x', Status: 2 }, V8.DbTrans);
V8.ApiEngine.Run('order-hook', { Id: 'x' }, V8.DbTrans);

关键区别| 表单对象描述「要写什么」,事务参数描述「在哪个事务里写」。两者混在一起,第二个问题就永远得不到回答。

02|参数是怎么从 JS 走到 C# 的

V8 引擎把 C# 的上下文对象直接暴露为 JS 全局变量 `V8`,JS 调用按位置绑定到 CLR 重载。事务对象的类型是 `Dos.ORM.DbTrans`,它出现在方法的最后一个位置参数上。

从 JS 调用到事务提交的调用链

调用链解释图(确定性渲染),依据当前实现梳理。

同步入口 `UptFormData(dynamic dynamicParam, DbTrans _trans = null)`:第一个参数是表名与表单数据,第二个参数才是事务。框架内部对事务参数只做一次恒等透传,不存在从表单字段兜底寻找事务的逻辑——写进对象里的 _trans 永远不会进入参数位置。

  • 表单对象:字段与值,描述数据。
  • 位置参数:事务、分页等执行上下文,描述行为。
  • 安全代理:脚本可见的事务句柄,控制提交权。

03|不传事务时,框架会自己开始一个事务

更新实现里的两行代码决定了全部行为:先判断事务是不是从外面传进来的,再决定用传入的还是新开一个。

var isOutsideTrans = _trans != null;
var trans = _trans == null ? dbSession.BeginTransaction() : _trans;

同一个事务对象会继续传给表单元事件与嵌套接口引擎;提交动作则严格限定——只有当前层自己开始的事务,才会在结束时提交。

if (!isOutsideTrans && !trans.IsCommitOrRollback)
{
    trans.Commit();
}

传入事务、未传事务、写进对象、脚本提交四种边界

状态边界解释图(确定性渲染),四种写法的处理路径并不相同。

04|写进表单对象会发生什么

表单对象最终会被转换成强类型参数。转换器只认识自己的属性,多出来的键不会报错,而是被忽略;随后在写库阶段,这些键会作为候选字段去匹配表结构,匹配不到就跳过。`_trans` 既不会被 C# 参数接收,也不会被当成字段写入。

结果是双输:参数位置上的 _trans 仍然是空(于是新开事务),对象里的 _trans 被静默丢弃,连一条错误都没有。这就是静默失效的完整链路。

失败边界| 不报错不等于在同一个事务里。跨表原子性只有在第三个参数位置传入 V8.DbTrans 时才成立。

05|安全代理:脚本看得见事务,但不能自己提交

V8 里拿到的 V8.DbTrans 是框架持有的安全代理,而不是原始事务句柄。它保留「传给下一个引擎继续共享」的能力,但脚本直接调用 Commit/Rollback 会被拦截并写入诊断事件。

这解决了一个长期风险:提交时机由框架的返回值规则管理,脚本自行提交会让事务生命周期失控,也让失败后的回滚无从谈起。

  • 接口引擎 return Code=1 自动提交、Code≠1 自动回滚。
  • 表单事件返回 Code=0 阻止提交并回滚。
  • 框架外层开始时,子调用只共享事务、不提交。
  • 禁止脚本从安全代理取内部事务自行提交。

06|把这条规则写进校验

这条约束不只是文档约定。仓库里既有可运行的 Node 测试断言「事务必须位于第三个参数」,发布流水线也用静态规则拒绝一切把事务对象塞进表单数据的写法。

源码断言与 Node 测试的执行记录

本地执行记录(确定性渲染):11 项源码断言与 2 项 Node 测试。

本文的技术证据来自当前源码区域与两项真实执行:11 项源码断言、2 项 Node 测试。源码事实、本地验证与线上行为在证据文件中分别标注,读者可以按同样的脚本复核。

07|交付边界

需要写清楚的边界有三条:源码签名证明的是当前实现;本地断言与测试证明的是这些规则在当前工作区可复现;线上行为仍取决于具体部署版本与环境,证据文件里没有把它表述为生产结论。

交付边界| 事务共享的正确写法是第三个位置参数;错误写法会静默新开事务;脚本不能自行提交。

把参数位置当成接口的一部分,把静默失效当成最贵的缺陷——下一次写跨表逻辑前,先确认事务在第几个位置。

内容说明:文中已标注的概念图由AI生成;源码与实测证据均来自当前工作区。竖卡的准确文案由确定性排版叠加,封面、调用链图、状态边界图与实测图均由确定性程序绘制,图注已分别说明。

Logo

宁波官方开源宣传和活动阵地,欢迎各位和我们共建开源生态体系!

更多推荐