全部文档
文档中心DeepModel功能触发器(Trigger)

触发器(Trigger)

触发器用于在新建、更新、删除对象数据后,自动执行一段 DeepQL 语句,实现数据校验、日志记录、联动更新等后置逻辑。

注意:触发器不能修改发起操作的对象本身(如需修改,请使用数据重写功能)。触发器会使保存数据增加额外开销,请合理使用。


触发器的执行语句与触发它的原始操作(insert / update / delete)在同一个事务中执行。如果触发器中的断言失败或语句报错,整个操作会被回滚——这是数据校验能生效的基础。

触发器在对象类型的 SDL 定义中声明,完整格式如下:

Copy
type 对象类型 {
    # ... 属性和链接定义 ...

    trigger 触发器名称
        after {insert | update | delete} [, ...]
        for {each | all}
        do (表达式);
};

关键字

说明

trigger 名称

触发器编码,同一对象内唯一

after

触发时机,在操作之后执行

insert, update, delete

运行时机,可多选,逗号分隔

for each

单条模式:对每条受影响的数据执行一次,__new__/__old__ 为单个对象

for all

批量模式:只执行一次,__new__/__old__ 为受影响的数据集合

do (表达式)

触发时执行的 DeepQL 表达式

变量

可用时机

含义

__new__

insert、update

操作的数据

__old__

update、delete

操作的数据

  • for each 模式下,__new__ / __old__ 是单个对象实例

  • for all 模式下,__new__ / __old__ 是对象的集合(可能包含多条数据)

触发器中执行的任何查询,看到的都是触发操作完成后的数据库状态。例如,在 insert 触发器中查询 detached Order,结果会包含刚刚插入的那条记录。


在 DeepModel 中,通过可视化界面配置触发器:对象属性栏 → 高级 → 新建触发器

配置项

说明

编码

自动生成,无需手动填写

名称

触发器的描述文字

运行时机

新建(insert)/ 更新(update)/ 删除(delete),可多选

执行方式

单条(for each)或 批量(for all)

执行语句

格式为 do (expr),保存后可在 SDL 详情中查看完整定义

运行时机针对最终生成的 DeepQL 语句。例如:

  • 在数据管理或 UX 页面新建包含自我链接的数据时,底层实际为「先 insert,再 update 自我链接」,会分别触发 insert 和 update 的触发器

  • 批量更新时如果底层是逐条执行的 DeepQL,即使触发器配置为「批量」,每次的 __new__ 也只有单条数据

  • 同一对象可以配置多个触发器,它们在同一事务中依次执行

  • 触发器可以引起其他对象的触发器被触发(级联触发)。系统会先完成第一阶段的所有触发器,再启动第二阶段,依此类推

  • 但如果触发器之间形成循环(A 触发 B,B 又触发 A),系统会报错

所有触发器的语句和原始操作最终会被编译为一条组合 SQL。如果多个触发器中有对同一对象的多次 update,只有一次会生效。因此,对同一对象的更新应尽量合并到一个触发器的一次 update 中。


目标:记录对象的新建、更新、删除操作到日志表,便于审计追溯。

步骤 1:新建日志对象 Log

编码

名称

分类

类型

说明

code

编码

非计算属性

文本

业务主键,可配置数据重写 <str>.id 自动填充

action

动作

非计算属性

枚举值

insert / update / delete

operation_time

操作时间

非计算属性

日期时间

数据重写 cal::local_datetime_of_statement() 自动填充

operate_user_id

操作用户ID

非计算属性

文本

数据重写 global ${空间模块}::current_user_id 自动填充

object_code

对象编码

非计算属性

文本

记录操作对象,建议加索引

business_key

业务主键

非计算属性

文本

记录操作数据的主键,建议加索引

data

数据

非计算属性

文本

记录操作后(或删除前)的对象数据快照

operator

操作用户

计算链接

${空间模块}::SystemUser

根据 operate_user_id 联查用户信息

步骤 2:为需要记录日志的对象配置触发器

以 Requirement 对象为例,配置三个单条触发器:

记录新建日志(运行时机:新建,执行方式:单条):

Copy
do (
    insert Log {
        action := 'insert',
        object_code := 'Requirement',
        business_key := __new__.code,
        data := to_str(<json>(
            select __new__ {
                name,
                req_type,
                component_code := .component.code
            }
        ))
    }
)

记录更新日志(运行时机:更新,执行方式:单条):

Copy
do (
    insert Log {
        action := 'update',
        object_code := 'Requirement',
        business_key := __new__.code,
        data := to_str(<json>(
            select __new__ {
                name,
                req_type,
                component_code := .component.code
            }
        ))
    }
)

记录删除日志(运行时机:删除,执行方式:单条):

Copy
do (
    insert Log {
        action := 'delete',
        object_code := 'Requirement',
        business_key := __old__.code,
        data := to_str(<json>(
            select __old__ {
                name,
                req_type,
                component_code := .component.code
            }
        ))
    }
)

注意:删除时使用 __old__(操作前的数据),新建和更新时使用 __new__(操作后的数据)。

技巧:将操作数据通过 <json>(select __new__ { ... }) 转为 JSON 再 to_str,可以灵活选择要记录的字段,也支持通过链接带出关联数据(如 .component.code)。

配置完成后,可新建 UX 页面展示操作日志:


场景:头表 Head 和行表 Detail 各有 status 属性。当头表状态从 true 改为 false 时,自动将关联的行表数据状态也置为 false。

在头表对象上配置触发器(运行时机:更新,执行方式:单条):

Copy
do (
    update Detail
    filter .head.code = __new__.code
    set {
        status := (
            false if (__old__.status and not __new__.status) else .status
        )
    }
)

解读

  • __old__.status and not __new__.status 表示「原来是 true、现在改成了 false」

  • 满足条件时将行表的 status 置为 false,否则用 .status 保持原值不变

  • filter .head.code = __new__.code 限定只更新当前头表数据关联的行表

注意:联动 update 要确保不会形成触发器循环(A 改 B → B 的触发器又改 A)。


通过 assert 函数进行校验:assert(条件, message := '错误信息')。条件为 false 时,保存失败并弹出错误信息,整个事务回滚。

触发器校验主要用于涉及多个属性/链接的复合条件。单个属性的简单约束(如必填、唯一、正则等)建议直接在属性的约束配置中设置,无需触发器。

示例 1:计划开始时间必须小于结束时间

对象 Task,运行时机:新建 + 更新,执行方式:单条:

Copy
do (
    assert(
        __new__.p_start_date < __new__.p_end_date,
        message := '任务【' ++ __new__.name ++ '】计划开始时间需小于计划结束时间'
    )
)

示例 2:重复需求的关联需求必填

对象 Requirement,运行时机:新建 + 更新,执行方式:单条:

Copy
do (
    assert(
        not (__new__.is_duplicated and not exists __new__.related_req),
        message := '需求【' ++ __new__.name ++ '】为重复需求,关联需求必填'
    )
)

当一个对象有多条校验规则时,可以把它们写在同一个触发器里,用 with 组织多条校验逻辑,最后用 assert 统一判断。好处是减少触发器数量,且多条规则的错误信息可以合并输出。

场景:对象 ValDemo 有属性 code(编码)、str1(文本)、num1(数字)、num2(数字)、status(枚举:s1/s2/s3)。需要校验:

  • 规则 1:状态为 s1 时,str1 必填

  • 规则 2:状态为 s2 时,num1 必须大于 num2

对应的 SDL 定义(运行时机:新建 + 更新,执行方式:批量):

Copy
trigger trigger1_7S
    after update, insert
    for all do (with
        -- 规则1:状态为 s1 时,str1 必填
        val1 :=
            (select __new__
             filter (.status = 's1') and not (exists (.str1))
            ),
        val1_msg :=
            (('状态为s1时, str1必填 | 具体: '
              ++ array_join(array_agg(val1.code), ', ')
             ) if exists (val1) else ''),

        -- 规则2:状态为 s2 时,num1 必须大于 num2
        val2 :=
            (select __new__
             filter (.status = 's2') and (.num1 < .num2)
            ),
        val2_msg :=
            (('状态为s2时, num1 应大于 num2 | 具体: '
              ++ array_join(array_agg(val2.code), ', ')
             ) if exists (val2) else ''),

        -- 统一断言:所有规则都通过才放行
        select
            assert(
                not (exists (val1)) and not (exists (val2)),
                message := (val1_msg ++ '\n') ++ val2_msg
            )
    );

逐段解读

部分

作用

for all

批量模式,__new__ 是本次操作的全部数据集合

val1 := (select __new__ filter ...)

从中筛出违反规则 1 的记录(状态为 s1 但 str1 为空)

val1_msg := ... if exists (val1) else ''

有违规时拼接错误信息 + 违规编码列表;无违规则为空串

val2 / val2_msg

同理处理规则 2

assert(not exists(val1) and not exists(val2), ...)

任一规则违反就 assert 失败,回滚事务

关键技巧

  • array_join + array_agg:把违规记录的编码收集为数组再拼成字符串,方便定位是哪些数据出了问题

  • 多规则合并:每条规则独立检查,错误信息用 '\n' 换行拼接,用户看到完整的错误描述

  • 批量 + select filter 模式:比为每条规则单独建触发器更简洁,且所有规则在同一个事务中完成

触发效果

导入或编辑数据时,违反规则 1 的提示「状态为s1时, str1必填」并列出违规编码;违反规则 2 的提示「状态为s2时, num1 应大于 num2」:

场景:头表 Head 和行表 Detail,删除行表数据后,每条头表至少保留一条关联的行表数据。

行表对象设置触发器(运行时机:删除,执行方式:批量):

Copy
do (
    assert(
        (for h in __old__.head union (
            select count((
                select detached Detail filter .head = h
            )) > 0
        )),
        message := '至少有一条关联的行表数据'
    )
)

解读

  • __old__.head 取出被删除的行表数据关联的所有头表

  • for h in ... union (...) 遍历每个头表,检查在删除后(detached Detail,即数据库当前状态)是否还有关联的行表

  • 使用 detached 确保查询的是全局的 Detail,而非受触发器上下文限制的集合

注意:在 UX 页面新建头行数据时,头表和行表是同时提交的,无法通过触发器校验行表是否有数据。此时需要在 UX 页面配置前端控制——保存时检查行表有数据才提交。


能力

说明

SDL 语法

trigger 名称 after insert, update for each do (expr)

运行时机

insert / update / delete,可多选

执行方式

for each(单条)或 for all(批量)

__new__ / __old__

操作后 / 操作前的数据,单条为对象,批量为集合

事务一致性

触发器与原始操作在同一事务,assert 失败则整体回滚

日志记录

触发器中 insert 日志对象,记录操作快照

联动更新

触发器中 update 其他对象,实现级联状态同步

单条校验

assert(条件, message := ...) 校验多属性组合

复合校验

with 中定义多条规则,分别 select 违规数据,统一 assert

批量校验

for … union 遍历集合逐条检查

级联触发

触发器可引发其他触发器,但不能形成循环

多 update 合并

同一对象的多次 update 只生效一次,需合并到一个触发器中

不能做的事

不能修改发起对象本身(需用数据重写)

回到顶部

咨询热线

400-821-9199

我们使用 ChatGPT,基于文档中心的内容以及对话上下文回答您的问题。

ctrl+Enter to send