Infor LN 4GL 开发技巧
Infor LN 4GL(源自 Baan 4GL)是 Infor LN ERP 的核心编程语言。本页汇总实战开发技巧、最佳实践和常见陷阱,帮助开发者提升编码效率和代码质量。
适用人群 :Infor LN 开发者、实施顾问、二次开发工程师
📋 目录
一、4GL 语言基础
1.1 数据类型(6 种核心类型)
类型
说明
示例
long
整数,32 位有符号
long i = 0
double
浮点数,8 字节 IEEE 754
double amount = 0.0
boolean
布尔值
boolean flag = true
string
字符串(声明时指定长度)
string name(30)
table
表格变量(数组/临时表)
table t_temp
domain
域类型(引用数据字典)
domain tcitem item_code
1.2 变量修饰符
long g_counter | 局部变量
extern long g_counter | 外部(全局)变量,可被其他程序访问
static long last_value | 静态变量,函数调用间保持值
const long max_retries = 3 | 常量
1.3 字符串操作
| 拼接用 &(不是 +)
string result(60)
result = "Hello" & " " & "World"
| 获取长度
long char_count = len(s) | 字符数
long byte_count = byte.len(s) | 字节数(多字节安全)
| 截取子串
string sub = s(1;5) | 从第1位取5个字符
| 去空格
s = strip$(s) | 去首尾空格
s = ltrim$(s) | 去左空格
s = rtrim$(s) | 去右空格
⚠️ 常见陷阱 :字符串拼接用 &,不是 +。+ 用于数值加法,对字符串会尝试隐式类型转换导致意外结果。
1.4 控制流
| if-then-else
if condition then
| ...
else
| ...
endif
| on case(推荐替代多层 if-else)
on case variable
case 1:
| ...
case 2, 3: | 支持多值
| ...
case default:
| ...
endcase
| 循环
for i = 1 to 10
| ...
endfor
while condition
| ...
endwhile
repeat
| ...
until condition
二、DAL 开发实战
2.1 DAL1 vs DAL2 对比
特性
DAL1
DAL2(推荐)
新建
dal.new()
dal.new.object()
修改
dal.update()
dal.change.object() + dal.set.field() + dal.save.object()
删除
dal.delete()
dal.delete.object()
字段依赖
❌ 不支持
✅ 自动触发
Hooks
部分触发
✅ 完整触发
性能
稍快
略慢(功能更全)
2.2 DAL2 标准工作流(Insert)
#include <bic_dam>
function long insert_record()
{
long ret
string dal.name(20)
dal.name = "twhinh215"
| Step 1: 开始新建
ret = dal.new.object(dal.name)
if ret <> 0 then
message("dal.new.object failed: %d", ret)
return(ret)
endif
| Step 2: 设置字段
dal.set.field("whinh215.item", "ITM000123")
dal.set.field("whinh215.cwar", "WH001")
dal.set.field("whinh215.sqty", 100.0)
| Step 3: 保存
ret = dal.save.object(dal.name)
if ret <> 0 then
| 错误处理
if ret = DALHOOKERROR then
message("Hook blocked the save")
endif
return(ret)
endif
return(0)
}
2.3 DAL2 标准工作流(Update)
function long update_record()
{
long ret
string dal.name(20)
dal.name = "twhinh215"
| Step 1: 加锁(重要!)
twhinh215.item = "ITM000123"
twhinh215.cwar = "WH001"
ret = db.bind(twhinh215, db.FIND.BY.KEYS, db.LOCK)
if ret <> 0 then
if ret = db.error.DBRECORDLOCKED then
message("Record is locked by another user")
endif
return(ret)
endif
| Step 2: 开始修改
ret = dal.change.object(dal.name)
if ret <> 0 then
db.release(twhinh215)
return(ret)
endif
| Step 3: 设置要修改的字段
dal.set.field("whinh215.sqty", 200.0)
| Step 4: 保存
ret = dal.save.object(dal.name)
db.release(twhinh215) | 别忘了释放锁!
return(ret)
}
2.4 dal.save.object() 返回值速查
返回值
含义
处理建议
0
成功
清理异常
DALHOOKERROR
Hook 阻止了保存
检查 Hook 逻辑
DALNOSETPERM
无表级权限
检查用户权限配置
DALNOOBJPERM
无记录级权限
检查记录级授权
> 0
数据库错误
检查字段约束和引用完整性
⚠️ 关键陷阱 :dal.change.object() 不会加锁 !多人并发修改同一记录时必须手动加锁(用 db.bind() 或 SELECT FOR UPDATE)。
2.5 DAL Hook 执行顺序
dal.save.object() 触发顺序:
1. 检查表级权限
2. 对每个已设置字段:fieldname.make.valid()
3. 触发依赖字段的 field.update()
4. 检查记录级权限
5. 触发 method.is.allowed()
6. 触发字段级 Hooks
7. 触发 before.save.object()
8. 执行实际保存(INSERT 或 UPDATE)
9. 触发 after.save.object()
三、Extension 开发机制
3.1 为什么用 Extension
方式
升级影响
维护难度
推荐度
直接修改标准 Script
❌ 升级覆盖
高
❌ 不推荐
User Exit(UE)
⚠️ 部分保留
中
⚠️ 旧方案
Extension
✅ 不受影响
低
✅ 推荐
3.2 Extension 类型
类型
执行时机
用途
Before DAL
DAL 操作之前
字段校验、默认值
After DAL
DAL 操作之后
联动更新其他表
Before Logic
标准逻辑之前
拦截/修改输入
After Logic
标准逻辑之后
补充处理
3.3 Extension 开发模板
| Extension DLL: tdslsdllcustom
| 扩展点: After Save Sales Order Line
declaration:
| 声明外部变量和函数
before.program:
| 初始化逻辑(可选)
after.save.object:
| 在 after.save.object Hook 中处理
if action = DAL_NEW then
| 新建后逻辑
elif action = DAL_UPDATE then
| 修改后逻辑
endif
四、Public Interfaces 调用
4.1 什么是 Public Interfaces
Public Interfaces 是 LN 提供的标准业务功能方法,可在 Extension 中调用,实现标准功能的复用,升级不受影响 。
4.2 常用 Public Interfaces
Common 模块
函数
说明
Common.ConvertAmount()
货币金额转换
Common.ConvertQuantity()
数量单位转换
Common.RoundAmount()
金额四舍五入
Common.GenerateFirstFreeNumber()
生成首个自由编号
Common.ConvertAmountToWords()
金额转文字
BusinessPartner 模块
函数
说明
BusinessPartner.GetGeneralData()
获取业务伙伴通用数据
BusinessPartner.GetShipToData()
获取送达方数据
BusinessPartner.GetPayByData()
获取付款方数据
Sales 模块
函数
说明
Sales.GetOrderSettings()
获取销售订单设置
Sales.CalculatePlannedDeliveryDate()
计算计划交货日期
SalesOrder.CreditCheck()
信用检查
4.3 调用语法模板
| 声明函数原型
extern long Common.ConvertAmount(
domain tccmp iFinancialCompany,
domain tcamnt iSourceAmount,
domain tcccur iSourceCurrency,
domain tcrtyp iExchangeRateType,
domain tcdate iRateDateUTC,
domain tcccur iTargetCurrency,
ref domain tcamnt oTargetAmount,
ref domain tcmcs.s999m oExceptionMessage mb,
ref long oExceptionID )
| 调用并处理异常
long ret
domain tcmcs.s999m oMsg mb
long oID
ret = Common.ConvertAmount( 575, amount, "USD", 1, date.to.utc(), "EUR", converted.amount, oMsg, oID )
if ret = 0 then
Exception.Delete(exception.id)
else
Exception.GetMessage( oID, 1, oMsg )
message( "金额转换失败: " & oMsg )
raise error
endif
五、Session 开发技巧
5.1 Session 类型
类型
用途
说明
maint.session
数据维护
增删改查
multi-occ.session
多记录列表
带格子的主从结构
single-occ.session
单记录编辑
表单式
dialog.session
对话框
参数输入
print.session
报表打印
启动 Report
5.2 常用 Section 顺序
declaration: | 变量声明
before.program: | 初始化
after.program: | 清理
before.open.object: | 打开对象前
after.open.object: | 打开对象后
before.close.object:| 关闭对象前
form.1: | 表单逻辑
field.1: | 字段级逻辑(when.field.changes 等)
choice.cont.process:| 按钮点击
六、Report 开发技巧
6.1 Report Section 顺序
before.program: | 初始化变量
after.program: | 清理资源
before.layout: | 每页开始
after.layout: | 每页结束
detail.1: | 明细行(每条数据)
after.detail.1: | 明细行后处理
header.1: | 表头
footer.1: | 表尾
6.2 报表分页技巧
detail.1:
before.section:
| 检查剩余行数,不足则分页
if r.report.line.remaining() < 5 then
r.send.new.page()
endif
after.section:
| 每行后递增计数器
line.count = line.count + 1
七、调试与排错
7.1 调试方法
方法
说明
message()
弹出消息框(最常用)
put.var()
输出到调试日志
append.var()
追加到变量
sleep()
暂停执行(调试时用)
LN Debug 工具
LN 自带的调试器
7.2 常见错误及解决
错误
原因
解决方案
dal.save.object() 返回 DALHOOKERROR
Hook 函数阻止保存
检查 DAL Hook 逻辑
db.error.DBRECORDLOCKED
记录被其他用户锁定
等待或提示用户
DALNOSETPERM
无表级权限
检查用户权限配置
字符串拼接结果异常
用了 + 而非 &
改用 & 拼接
db.bind() 找不到记录
主键值不正确
检查主键值
7.3 异常处理标准模板
ret = SomePublicInterface( ..., oMsg, oID )
if ret = 0 then
Exception.Delete(exception.id)
else
Exception.GetMessage( oID, 1, oMsg )
message( "错误: " & oMsg )
raise error
endif
八、性能优化
8.1 SQL 优化
| 不推荐:逐行查询
select twhinh215.*
from twhinh215
where twhinh215.item = :item
selectdo
| 逐行处理
endselect
| 推荐:批量处理
table t_temp
select twhinh215.item, twhinh215.cwar, sum(twhinh215.sqty):total
from twhinh215
where twhinh215.item = :item
group by twhinh215.item, twhinh215.cwar
having sum(twhinh215.sqty) > 0
selectdo
| 批量处理
endselect
8.2 避免常见性能陷阱
陷阱
说明
建议
循环内 SELECT
每次查询都访问数据库
使用 TABLE 变量缓存
不使用索引
WHERE 条件未命中索引
确保查询字段有索引
过多的 message()
频繁弹窗影响性能
调试后删除
未释放锁
db.bind() 后忘记 db.release()
始终配对使用
九、代码规范
9.1 命名约定
类型
约定
示例
函数
三个字母模块前缀 + 动词
tdsls.calculate.tax()
变量
小写 + 点号分隔
local.counter
常量
全大写
MAX_RETRY_COUNT
DAL 对象
表名作为字符串
dal.name = "twhinh215"
9.2 注释规范
| 4GL 使用 | 作为行注释符
| 函数说明写在函数定义之前
| 复杂逻辑添加内联注释
9.3 错误处理规范
| 推荐:完整的错误处理
ret = dal.save.object(dal.name)
if ret <> 0 then
| 具体错误处理
return(ret)
endif
| 不推荐:忽略返回值
dal.save.object(dal.name)
十、学习资源
官方资源
第三方资源
资源
说明
链接
旺财学 Infor LN ERP
崔文远运营的中文技术公众号
微信搜索关注
FullOnBaan
LN 技术知识库(英文)
fullonbaan.com
CSDN Infor 标签
中文技术文章合集
搜索 "Infor LN"
知识星球
旺财学 Infor LN ERP 社群
加入
10.2 ODBC/JDBC SQL 参考
LN 的 ODBC/JDBC 连接器使用独立的 SQL 方言(区别于 4GL 嵌入式 SQL),以下是常用速查。
SQL 数据类型
SQL 类型
说明
对应 Infor ES 数据库类型
integer
32 位有符号整数
char, int, long, enum, bitset
real
近似数值(浮点数)
float, double
string
字符串
string, multibyte string
date
日期 [0001-01-01 ~ 9999-12-31]
date
timestamp
时间戳(UTC)
time (UTC)
UTC 时间戳模式
模式
ByteCountOfUtc
BitCountOfUtc
激活方式
Utc32 (默认)
4
32
资源 "utc40" 未设置或设为 0
Utc40
5
40
资源 "utc40" 设为非 0 值
SQL 函数速查
函数
说明
示例
SUBSTRING(src FROM start FOR len)
截取子字符串
SUBSTRING('abcdef' FROM 3 FOR 2) → 'cd'
TRIM([[LEADING\|TRAILING\|BOTH] char FROM] src)
去除首尾字符
TRIM(' abc ')
UPPER(s) / LOWER(s)
大小写转换
CURRENT_DATE
返回当前日期
CURRENT_TIMESTAMP
返回当前时间戳(UTC)
EXTRACT(field FROM expr)
提取日期字段
EXTRACT(YEAR FROM CURRENT_DATE)
COALESCE(e1, e2, ...)
返回第一个非 NULL 值
COALESCE(desc, 'N/A')
CAST(expr AS type)
类型转换
ML_ONE_LANG(col[, lang])
返回单一语言值
ML_ONE_LANG(a.dscr)
TEXT_CONTENT(col[, lang])
检索文本引用列内容
SQL 注释
-- 单行注释(两个连字符 + 空格)
SELECT * FROM dbtst120 -- 从员工表查询
最后更新:2026-05-08
💡 本页内容由 Infor LN 4GL 编程开发助手 Skill 输出,基于 Infor LN 2604 官方编程指南和 Public Interfaces 参考手册。如有错误或补充,欢迎通过 GitHub Issue 反馈!