用mendix实现: ID属性按照“年份+0001”格式自动生成,年份为当前年份,序号为当年已创建实体数+1
小艺001
2025.10.23 16:19发布于MX How To技术分享
2831

在 Mendix 中实现这个自动 ID 生成逻辑,最佳实践是使用实体的 “提交前” (Before Commit) 事件处理微流。这样做可以确保无论通过何种方式创建对象(例如,通过页面新建、API导入等),这个ID生成逻辑都会被触发,保证了数据的一致性。

下面是详细的步骤和微流实现说明。

前期准备

  1. 实体 (Entity):假设你有一个实体,我们称之为 MyObject
  2. ID 属性:在 MyObject 实体中,创建一个名为 ID 的属性,其类型(Type)必须是字符串(String)
  3. 启用创建日期:确保 MyObject 实体在属性中勾选了系统成员 createdDate。这在Mendix中是默认启用的。

步骤一:创建 “提交前” (Before Commit) 微流

  1. 双击你的实体 MyObject,打开其属性对话框。
  2. 切换到 “事件处理程序” (Event handlers) 标签页。
  3. 点击 “提交前” (Before Commit) 事件的 “选择…”(Select…) 按钮,然后选择 “新建”(New) 来创建一个新的微流。
  4. 为微流命名,一个好的命名规范是 BCO_MyObject_GenerateID (BCO 代表 Before Commit)。
  5. 点击“确定”,Mendix会自动为你创建一个微流,并传入一个 MyObject 类型的参数。

步骤二:设计微流逻辑

这个微流的目标是生成ID并赋值给传入的 $MyObject 对象。以下是微流中的活动(Activity)顺序和配置。


微流概览图

[Start] -> (Create YearString) -> (Retrieve Objects) -> (Aggregate List) -> (Create SequenceNumber) -> (Create FormattedSequence) -> (Change MyObject) -> [End]


1. 创建变量:获取当前年份 (Create Year Variable)

  • 活动类型: 创建变量 (Create Variable)
  • 数据类型: 字符串 (String)
  • 值/表达式: formatDateTime([%CurrentDateTime%], 'yyyy')
  • 输出变量名: YearString

说明: 这个活动使用 formatDateTime 函数从系统变量 [%CurrentDateTime%] 中提取出四位数的年份字符串,例如 “2023”。

2. 检索对象:获取当年已创建的对象总数 (Retrieve Objects)

  • 活动类型: 检索 (Retrieve)
  • 来源: 从数据库 (From Database)
  • 实体: MyObject
  • 范围: 全部 (All)
  • XPath 约束: [year-from-dateTime(createdDate) = toInteger($YearString)]
  • 输出: 列表 (List),命名为 MyObjectList_ThisYear

说明: 这是关键的一步。

  • year-from-dateTime(createdDate): 这是一个 XPath 函数,用于从每个对象的 createdDate 属性中提取年份(整数类型)。
  • toInteger($YearString): 我们将前面创建的年份字符串(如 “2023”)转换为整数,以便进行比较。
  • 这个XPath约束会筛选出所有 createdDate 在当年的 MyObject 对象。

3. 聚合列表:计算对象数量 (Aggregate List)

  • 活动类型: 聚合列表 (Aggregate List) 或 列表操作 (List Operation) (在较新版Mendix中)
  • 输入列表: $MyObjectList_ThisYear
  • 函数: 计数 (Count)
  • 输出变量类型: 整数 (Integer)
  • 输出变量名: CountThisYear

说明: 这个活动计算上一步检索到的列表中的对象数量。如果今年还没有创建任何对象,$CountThisYear 的值将是 0

4. 创建变量:计算新序号 (Create Sequence Number Variable)

  • 活动类型: 创建变量 (Create Variable)
  • 数据类型: 整数 (Integer)
  • 值/表达式: $CountThisYear + 1
  • 输出变量名: SequenceNumber

说明: 将当年的对象总数加 1,得到新的序号。例如,如果已有3个对象,新序号就是 4

5. 创建变量:格式化序号(补零) (Create Formatted Sequence Variable)

  • 活动类型: 创建变量 (Create Variable)
  • 数据类型: 字符串 (String)
  • 值/表达式: substring(toString(10000 + $SequenceNumber), 1)
  • 输出变量名: FormattedSequence

说明: 这是 Mendix 中一个常用的小技巧,用于给数字左侧补零。

  • 10000 + $SequenceNumber: 假设 $SequenceNumber12,结果就是 10012。如果是 123,结果就是 10123
  • toString(...): 将数字转换为字符串,例如 "10012"
  • substring(..., 1): 从字符串的第二个字符(索引为1)开始截取到末尾。"10012" 就变成了 "0012""10123" 就变成了 "0123"
  • 这样,无论序号是几位数,我们都能得到一个固定的四位数、前面带零的字符串。

6. 更改对象:设置最终ID (Change Object)

  • 活动类型: 更改对象 (Change Object)
  • 对象: $MyObject (微流的输入参数)
  • 提交: 否 (No)。因为这是 “提交前” 微流,Mendix 会在微流执行完毕后自动提交对象。
  • 刷新在客户端: 是 (Yes)
  • 设置成员:
    • 点击 “新建” (New)
    • 成员: ID
    • 值/表达式: $YearString + $FormattedSequence

说明: 将年份字符串(如 “2023”)和格式化后的序号字符串(如 “0001”)拼接起来,形成最终的ID(“20230001”),并将其赋值给当前正在创建的 $MyObject 对象的 ID 属性。

重要考虑:并发问题(Race Condition)

当系统并发量很高时,可能会出现两个用户在同一毫秒内几乎同时创建对象的情况。他们执行这个微流时,可能会查询到相同的 $CountThisYear,从而生成了重复的ID。

解决方案

  1. 数据库唯一约束:最简单且强烈推荐的方法。在 MyObject 实体的 “验证规则” (Validation Rules) 标签页中,为 ID 属性添加一个 “唯一” (Unique) 约束。

    • 优点:数据库层面保证了ID的唯一性。如果出现并发导致ID重复,第二个尝试提交的事务会失败,Mendix会抛出一个错误。你可以捕获这个错误并提示用户“操作失败,请重试”。
    • 缺点:用户可能会看到错误提示。但在大多数场景下,这种并发冲突的概率极低,这是一个可接受的、健壮的解决方案。
  2. 使用锁机制或专门的序号生成器实体:对于极其严苛的系统,可以创建一个单独的“序号”实体来管理每年的计数器,并在生成ID时对该实体记录进行加锁。这种方法更复杂,只在上述唯一约束方案无法满足需求时才考虑。

通过以上步骤,你就成功地实现了一个健壮、自动化的ID生成器。


Mendix 自动生成 ID 微流实现 Q&A

问题 1:为什么选择使用“提交前” (Before Commit) 事件,而不是“创建后” (After Create) 或自定义的按钮微流?

回答:
这是为了保证 数据的一致性和逻辑的健壮性

  • “提交前” (Before Commit):这个事件在对象数据被写入数据库之前触发。它是最理想的位置,因为:
    • 通用性:无论对象是通过页面新建、API 调用、还是其他微流创建的,这个事件都会被触发。你只需在一个地方定义逻辑,就能覆盖所有创建场景。
    • 原子性:ID 的生成和对象的创建在同一个数据库事务中完成。如果ID生成失败,整个对象的创建也会失败,不会产生没有ID的“脏数据”。
  • “创建后” (After Create):这个事件在对象被实例化(在内存中创建)后立即触发,但此时对象还 未提交到数据库。如果你在这个微流里去数据库查询已有的对象数量,是查不到当前这个新对象的,这会导致计数逻辑变得复杂且容易出错。
  • 按钮微流:如果把逻辑放在一个“保存”按钮的微流里,那么只有通过点击这个按钮创建的对象才会有ID。如果将来有其他方式创建对象(如数据导入),你就必须复制粘贴这段逻辑,这违反了“不要重复自己”(DRY)的原则,难以维护。

结论:“提交前”事件是实现这类“创建时必须完成的业务逻辑”的最佳实践。


问题 2:微流中用于给序号补零的表达式 substring(toString(10000 + $SequenceNumber), 1) 是如何工作的?

回答:
这是一个在 Mendix 中非常常用且高效的技巧,用于给数字左侧补零,使其达到固定长度(这里是4位)。

让我们用一个例子来分解它,假设新序号 $SequenceNumber35

  1. 10000 + $SequenceNumber:计算 10000 + 35,结果是整数 10035。这个 10000 的作用是确保结果总是一个五位数。
  2. toString(...):将整数 10035 转换为字符串 "10035"
  3. substring(..., 1):从字符串的第二个字符(索引为1,Mendix中索引从0开始)开始截取,一直到字符串末尾。对于 "10035",截取后得到的结果就是 "0035"

如果 $SequenceNumber123,过程就是 10123 -> "10123" -> "0123"
如果 $SequenceNumber1,过程就是 10001 -> "10001" -> "0001"

通过这种方式,无论原始序号是几位数,我们都能得到一个格式统一的四位字符串。


问题 3:如果系统并发量很大,两个用户在同一时刻创建对象,会不会生成重复的 ID?如何解决?

回答:
会的,这存在“竞态条件”(Race Condition)风险。 两个并发的微流可能会同时从数据库中查询到相同的“当年对象总数”,然后计算出完全相同的 SequenceNumber,最终生成重复的ID。

最佳解决方案:设置数据库唯一约束。

  1. 打开你的实体 (MyObject) 的属性。
  2. 切换到 “验证规则” (Validation Rules) 标签页。
  3. 点击“新建”,规则类型选择 “唯一” (Unique),然后选择 ID 属性。
  4. 设置一个错误消息,例如:“ID发生冲突,请重新保存。”

工作原理:
即使两个微流生成了相同的ID,当它们尝试向数据库提交数据时,数据库层面的唯一约束会阻止第二个事务成功提交。Mendix 会捕获这个数据库错误,并将其作为验证失败呈现给用户。虽然用户可能需要重试一次,但这从根本上保证了数据的唯一性和准确性,是解决此类并发问题的最可靠、最简单的方法。


问题 4:如果我删除了一个当年的对象,它的序号会被后续创建的新对象重用吗?

回答:
不会。 我们的微流逻辑是基于“当前数据库中已存在的对象数量”来计算新序号的。

例如:

  1. 今年已创建3个对象,ID分别为 20230001, 20230002, 20230003
  2. 你删除了 20230002。现在数据库中只有2个今年的对象了。
  3. 此时,你再创建一个新对象。微流会查询到当前有2个对象,计算出的新序号是 2 + 1 = 3,生成的ID将是 20230003

哦,等等,这里会产生重复!这是一个很好的问题,它暴露了 Count + 1 逻辑的一个缺陷。

更优化的方案:获取最大序号

为了避免删除后ID重复的问题,我们应该获取当年的最大序号,然后加1。微流需要做如下调整:

  1. 检索 (Retrieve):和原来一样,获取当年所有对象列表 MyObjectList_ThisYear
  2. (新)决策 (Decision):检查 MyObjectList_ThisYear 是否为空 ($MyObjectList_ThisYear = empty)。
    • 如果为空 (true):说明是今年的第一个对象,直接将序号设置为1。
    • 如果不为空 (false):执行下一步。
  3. (新)列表操作 (List Operation)
    • 活动类型: 列表操作 (List Operation)
    • 输入列表: $MyObjectList_ThisYear
    • 操作: 查找 (Find)
    • 查找方式: 最大值 (Maximum)
    • 成员: ID (是的,直接对字符串ID找最大值)
    • 输出变量: MyObject_WithMaxID
  4. (新)创建变量 (Create Variable)
    • : toInt(substring($MyObject_WithMaxID/ID, 4))
    • 说明: 截取最大ID的后四位序号部分,并转换为整数。
    • 输出变量: MaxSequence
  5. 后续逻辑: 新的序号就是 $MaxSequence + 1

这个改进后的逻辑确保了序号总是递增的,即使中间有数据被删除,也不会造成ID重复。


问题 5:如果我的应用中每年会产生数百万条记录,每次都去数据库检索并计数,性能会不会很差?

回答:
可能会。 如果没有适当的优化,对一个包含数百万记录的表执行 count 操作会很慢。

解决方案:为 createdDate 属性添加数据库索引。

  1. 在你的 Mendix Studio Pro 中,打开你的实体 (MyObject)。
  2. 切换到 “索引” (Indexes) 标签页。
  3. 点击“新建”,将 createdDate 属性添加进去。
  4. 重新部署你的应用,Mendix 会在数据库中为这个字段创建索引。

作用: 索引会极大地加快数据库基于 createdDate 进行查询和筛选的速度。微流中的 XPath 约束 [year-from-dateTime(createdDate) = ...] 将会利用这个索引,使得检索性能从“全表扫描”提升为“索引查找”,即使在海量数据下也能保持高效。

首赞
收藏
手机查看
举报
1个评论
倒序看帖
仅看楼主
    西家吴彦祖
    2025.10.28 11:27 发布
    #1
    还是有个漏洞,就是“新建-提交”和“修改-提交”,会让同一个object变更编号。建议微流里要加上isNew()来判断是不是新建的object,这样edit后再提交就不会触发编号重写
    西家吴彦祖
    点赞
    评论
    举报