PBIDEA:用 uo_database 与 uo_database_pool 做数据库连接与连接池复用
PBIDEA:用 uo_database 与 uo_database_pool 做数据库连接与连接池复用阅读说明
1. 适用版本:PowerBuilder 10(PBIDEA 版本源 UPDATE\10 为基准);PB12.5 已实测同源码、同行为,代码可直接复用无需改写。
2. 支持数据库:SQL Server 2008 R2(本文全部示例的实测环境)。uo_database 另支持 ODBC / Oracle / MySQL / PostgreSQL / SQLite / DB2 / Sybase / SQLAnywhere / Firebird(InterBase) / SQLBase,连法相同,仅 DBMS 取值不同。
3. 操作系统与环境要求:Windows 7+;必须部署与 PB 版本匹配的 PbIdea.dll / Pbidea_cs.dll;工程库列表需同时挂载 sql.pbl 与 websuite.pbl;示例连接 127.0.0.1,18433(SQL Server 2008 R2 SP3,10.50.6000.34)。
4. 难度系数:★★★☆☆
5. 其它阅读说明:需先了解 transaction 对象的常用属性(DBMS/ServerName/Database/LogId/LogPass/AutoCommit/SQLCode/SQLErrText);示例会在 tempdb 建演示表 dbo.t_pbidea_demo,跑完自动删除,不影响现有数据。
一、为什么不要只用裸 SQLCA
写 PB 的人对 SQLCA 太熟了:一个全局的 transaction,CONNECT USING SQLCA; 一把梭。但真做项目会撞上四堵墙:
问题裸 SQLCA 的表现
多库SQLCA 是全局单例,要连第二个库就得再声明 transaction,连接参数散落各处
复用每次要连都得重新赋一遍 DBMS/LogId/LogPass,或写死在 ini 里再手工解析
参数化只能拼字符串,或者退回 PB 原生动态 SQL 的 PREPARE ... USING 四步式
连接池完全没有,多线程/多请求场景只能自己造轮子
PBIDEA 的 uo_database(在 sql.pbl)就是冲着这四点来的。它继承自 transaction,所以你熟的那套属性一个不少,但上面挂了一整套外部函数方法,连接方式、参数化取数、事务、连接池全都有。
二、uo_database 是什么
关键一句话:uo_database 是 transaction 的子类,不是普通 NVO。
global type uo_database from transaction
所以它自带 DBMS / Database / LogId / LogPass / ServerName / DBParm / AutoCommit / SQLCode / SQLErrText / SQLNRows 全套属性,用法和 SQLCA 一模一样,零学习成本。
生命周期
[*]constructor 自动调用 sqlCreate(),destructor 自动调用 sqlDestroy();
[*]所以是 create 即用、destroy 即释放,不需要手工初始化。
方法全表(对照 sql.pbl 源码实证)
方法返回说明
Open()boolean连接(需先设好属性)
Open(string configFile)boolean按配置文件连接
IsOpen()boolean是否已连接
IsAlive()boolean连接是否存活(探活用)
SetTransLevel(int level)—事务隔离级别 0–4
Close()—断开
CommitTrans() / RollbackTrans()—提交 / 回滚
ExecuteSQL(string sql, ...)uo_recordset执行 SQL,变参(可散参或 any 数组)
PrepareSQL(string sql)uo_recordset预处理语句
ExecuteScaler(string sql)any标量查询,只要一个值就用它
ExecuteScaler(sql, ref boolean success)any带成功标志
RowsAffected()long受影响行数
SaveConfig(string configFile)int把当前属性存成配置文件
Printf(string fmt, ...)int打印到控制台
事件:beginconnect() / endconnect(boolean ab_connect) / ondisconnect()。
DBMS 取值(源码注释原文)
"ODBC"、"MSS"(或 "Microsoft SQL Server" / "MSS Microsoft SQL Server")、"Oracle" / "O90" / "O12"(大写 O 打头皆视为 Oracle,需设 ThreadSafe="Yes")、"InterBase" / "Firebird"、"SQLBase"、"DB2"、"Sybase"、"MySQL"、"PostgreSQL"、"SQLite"、"SQLAnywere"。
三、环境准备:建表 SQL
下面这段在 SQL Server 2008 上建演示表,直接用查询分析器执行即可(示例对象运行时也会自己建一次,这里先给你看清楚表结构):
-- SQL Server 2008 语法:identity 自增 + decimal(12,2) 金额 + getdate() 默认时间
if object_id('dbo.t_pbidea_demo') is not null drop table dbo.t_pbidea_demo
go
create table dbo.t_pbidea_demo(
id int identity(1,1) primary key,
code varchar(20) not null,
name nvarchar(50)not null,
qty int not null default 0,
price decimal(12,2) not null default 0,
crt_time datetime not null default getdate()
)
go
前置:库列表挂 sql.pbl + websuite.pbl,运行目录放 PbIdea.dll / Pbidea_cs.dll。
四、第一个可运行示例:连接 + 建表
前置:新建 NVO nvo_dbpool_demo,库列表含 sql.pbl 与 websuite.pbl;运行目录已放 PbIdea.dll。
步骤:1) 把下面代码贴进 nvo_dbpool_demo;2) 在窗口按钮的 clicked 里写 nvo_dbpool_demo lnvo / lnvo = create nvo_dbpool_demo / lnvo.of_demo() / destroy lnvo;3) 运行点击,MessageBox 会输出每一步的执行结果。
// ===== 输入区:按你的实际环境修改 =====
uo_database ldb // uo_database 连接对象
string ls_server // 服务器地址与端口,端口用逗号分隔
string ls_db // 数据库名
string ls_user // 登录名
string ls_pwd // 登录口令
ls_server = '127.0.0.1,18433'
ls_db = 'tempdb'
ls_user = 'sa'
ls_pwd = 'Zkkt@667788'
// 功能:准备一条 uo_database 连接(此时还没连,Open() 才真正建立连接)
// 要点:uo_database 继承自 transaction,属性名与 SQLCA 完全一致
ldb = create uo_database
ldb.DBMS = 'MSS' // SQL Server;等价写法 'Microsoft SQL Server'
ldb.ServerName = ls_server // 端口用逗号分隔,不能用冒号
ldb.Database = ls_db
ldb.LogId = ls_user
ldb.LogPass = ls_pwd
ldb.AutoCommit = true // true=自动提交;false=需手工 CommitTrans/RollbackTrans
// 功能:建表(SQL Server 2008 语法)
// 要点:DDL 没有结果集,成败只能看 adb.SQLCode / adb.SQLErrText
uo_recordset lrs
string ls_ret
ls_ret = ''
// PB 字符串里含单引号时,外层改用双引号包,不要写成 '' 双写(那是 T-SQL 不是 PowerScript)
lrs = ldb.ExecuteSQL("if object_id('dbo.t_pbidea_demo') is not null drop table dbo.t_pbidea_demo")
if ldb.SQLCode <> 0 then
ls_ret = 'DROP_FAIL code=' + String(ldb.SQLCode) + ' ' + ldb.SQLErrText
else
lrs = ldb.ExecuteSQL('create table dbo.t_pbidea_demo(id int identity(1,1) primary key, code varchar(20) not null, name nvarchar(50) not null, qty int not null default 0, price decimal(12,2) not null default 0, crt_time datetime not null default getdate())')
if ldb.SQLCode <> 0 then
ls_ret = 'CREATE_FAIL ' + ldb.SQLErrText
else
ls_ret = 'DDL_OK'
end if
end if
MessageBox('建表结果', ls_ret)
实测输出:DDL_OK。
五、参数化写入:三种写法
这里是本文最容易踩坑的地方,先说结论:
PBIDEA 的参数占位符是 :1 / :2 或 :名字,不是 ?。
? 是 PB 原生动态 SQL 的写法,搬到 uo_database 上会报「至少一个参数没有被指定值」。
三种写法全部实机跑通:
// ===== 输入区:写入内容与连接 =====
string ls_code // 商品编码
string ls_name // 商品名称
long ll_qty // 数量
double ld_price // 单价(注意:用 double,不要用 decimal)
uo_recordset lrs
string ls_ret
boolean lb_ok
uo_database ldb // 已 Open 的连接
ls_code= 'A001'
ls_name= '鼠标'
ll_qty = 10
ld_price = 59.50
ls_ret = ''
// 方式一:ExecuteSQL 变参,占位符 :1 :2 :3 :4
// 坑:变参位置不要直接写字面量,要用【已声明类型的变量】传,否则 DLL 认不出参数类型
lrs = ldb.ExecuteSQL('insert into dbo.t_pbidea_demo(code,name,qty,price) values(:1,:2,:3,:4)', ls_code, ls_name, ll_qty, ld_price)
if ldb.SQLCode <> 0 then
ls_ret = 'INS1_FAIL ' + ldb.SQLErrText
else
ls_ret = 'INS1 rows=' + String(ldb.RowsAffected())
end if
// 方式二:PrepareSQL + 按位置绑定(位置从 1 开始)
ls_code = 'A002'
ls_name = '键盘'
ll_qty = 5
ld_price = 129.00
lrs = ldb.PrepareSQL('insert into dbo.t_pbidea_demo(code,name,qty,price) values(:1,:2,:3,:4)')
lrs.BindParam(1, ls_code)
lrs.BindParam(2, ls_name)
lrs.BindParam(3, ll_qty)
lrs.BindParam(4, ld_price)
lb_ok = lrs.ExecuteSQL()
if lb_ok then
ls_ret = ls_ret + ' INS2 rows=' + String(lrs.RowsAffected())
else
ls_ret = ls_ret + ' INS2_FAIL ' + ldb.SQLErrText
end if
// 方式三:PrepareSQL + 按名字绑定
ls_code = 'A003'
ls_name = '显示器'
ll_qty = 2
ld_price = 899.00
lrs = ldb.PrepareSQL('insert into dbo.t_pbidea_demo(code,name,qty,price) values(:code,:name,:qty,:price)')
lrs.BindParam('code', ls_code)
lrs.BindParam('name', ls_name)
lrs.BindParam('qty', ll_qty)
lrs.BindParam('price', ld_price)
lb_ok = lrs.ExecuteSQL()
if lb_ok then
ls_ret = ls_ret + ' INS3 rows=' + String(lrs.RowsAffected())
else
ls_ret = ls_ret + ' INS3_FAIL ' + ldb.SQLErrText
end if
MessageBox('写入结果', ls_ret)
实测输出:INS1 rows=1 INS2 rows=1 INS3 rows=1。
三种怎么选:
写法适用场景备注
ExecuteSQL 变参一次性语句,图省事散参或 any 数组都行,数组形式适合 in(:args)
PrepareSQL + 位置绑定同一语句循环执行多次编译一次、绑定多轮,性能最好
PrepareSQL + 名字绑定SQL 里参数多、顺序易错可读性最好
BindParam 还有几个实用重载:出参必须带 ref(BindParam(1, ref ls_out, 0),nSize 为 0 时默认 1024);BindParamFile(pos, fileName) 可以直接把文件内容/URL 绑进字段;SetBlobBindType(0..4) 控制 blob 的绑定方式。
六、取数:uo_recordset 全貌
ExecuteSQL 返回的是 uo_recordset,destructor 会自动 Close()。
uo_recordset lrs
uo_json ljsons[]
string ls_ret
string ls_code
string ls_name
double ld_qty
double ld_price
long ll_rows
int li_n
any la_cnt
uo_database ldb // 已 Open 的连接
ls_ret = ''
ll_rows = 0
lrs = ldb.ExecuteSQL('select code,name,qty,price,crt_time from dbo.t_pbidea_demo order by id')
if lrs.HasResultSet() then
do while lrs.FetchNext()
ls_code = lrs.GetString('code') // 按字段名取
ls_name = lrs.GetString(2) // 按序号取(从 1 开始)
ld_qty = lrs.GetNumber('qty')
ld_price = lrs.GetNumber('price')
ll_rows = ll_rows + 1
ls_ret = ls_ret + '~r~nROW ' + ls_code + ' ' + ls_name + ' qty=' + String(ld_qty) + ' price=' + String(ld_price)
loop
ls_ret = 'QUERY rows=' + String(ll_rows) + ls_ret
else
ls_ret = 'QUERY NO_RESULTSET ' + ldb.SQLErrText
end if
// 转 JSON:整个结果集一次性取成 uo_json 数组
// 注意:上面的 do while 已经把 lrs 读完了,必须重新 ExecuteSQL 拿一个新结果集
lrs = ldb.ExecuteSQL('select top 2 code,name,qty from dbo.t_pbidea_demo order by id')
li_n = lrs.FetchJsons(ljsons)
if li_n > 0 then
ls_ret = ls_ret + '~r~nJSONS n=' + String(li_n) + ' first=' + ljsons.ToString()
else
ls_ret = ls_ret + '~r~nJSONS n=0'
end if
// 标量查询:只要一个值就用 ExecuteScaler
la_cnt = ldb.ExecuteScaler('select count(*) from dbo.t_pbidea_demo')
ls_ret = ls_ret + '~r~nSCALAR count=' + String(la_cnt)
MessageBox('取数结果', ls_ret)
实测输出:
QUERY rows=3
ROW A001 鼠标 qty=10 price=59.5
ROW A002 键盘 qty=5 price=129
ROW A003 显示器 qty=2 price=899
JSONS n=1 first=[{"code":"A001","name":"鼠标","qty":10},{"code":"A002","name":"键盘","qty":5}]
SCALAR count=3
注意 FetchJsons 返回的 n 是 1 —— 它返回的是一个整体 uo_json(数组),不是每行一个元素;ljsons 就是整份数据。
取值方法两套重载(按字段名 / 按序号,从 1 开始):GetNumber / GetString / GetBlob / GetDecimal / GetDate / GetTime / GetDateTime,另有 GetValue() 返回 any 由你自判类型。字段元信息走 GetFieldCount() + GetField(idx 或 name) 拿 uo_field(GetName/GetType/GetSize/GetPrecision/GetScale)。
⚠️ uo_field 里取小数的方法源码拼写是 GetDeciaml()(不是 GetDecimal),照抄,别"顺手改正",否则编译不过。
七、事务与隔离级别
AutoCommit = false 之后,所有写操作都进事务,必须显式提交或回滚:
// ===== 输入区:要写入的一行数据 =====
string ls_code // 编码
string ls_name // 名称
long ll_qty // 数量
double ld_price // 单价
uo_recordset lrs
string ls_ret
uo_database ldb // 已 Open 的连接
ls_code= 'B001'
ls_name= '事务行'
ll_qty = 1
ld_price = 9.90
ls_ret = ''
ldb.AutoCommit = false
// 0=ReadUncommitted 1=ReadCommitted 2=RepeatableRead 3=Serializable 4=Snapshot
ldb.SetTransLevel(1)
lrs = ldb.ExecuteSQL('insert into dbo.t_pbidea_demo(code,name,qty,price) values(:1,:2,:3,:4)', ls_code, ls_name, ll_qty, ld_price)
if ldb.SQLCode = 0 then
ls_ret = 'TX_COMMIT rows=' + String(lrs.RowsAffected())
ldb.CommitTrans()
else
ldb.RollbackTrans()
ls_ret = 'TX_ROLLBACK ' + ldb.SQLErrText
end if
ldb.AutoCommit = true
MessageBox('事务结果', ls_ret)
实测输出:TX_COMMIT rows=1。
两个细节:
[*]行数要在 CommitTrans() 之前读。提交之后 adb.RowsAffected() 会变成 -1,想拿行数就读 lrs.RowsAffected()。
[*]SetTransLevel 的参数是 int,直接写数字即可,不用常量。
八、连接池:uo_database_pool
四个方法,一张表讲清:
方法说明
CreatePool(string poolName, uo_database db)按模板连接建池,池名可多组共用
QueryPool(string poolName)取一条连接,用完必须还
GiveBackPool(uo_database db)归还(destroy 也会自动还,二选一)
DestroyPool()销毁池,释放池中全部连接
// 功能:连接池 —— 建池 / 取连接 / 使用 / 归还 / 销毁
// 要点:QueryPool 取出的连接用完必须 GiveBackPool;
// 池中连接 10 分钟无活动会被自动断开,长时间闲置后要先 IsAlive() 探活
uo_database ldb, ldb2
uo_database_pool lpool
uo_recordset lrs
string ls_ret
int li_ret
any la_cnt
ls_ret = ''
ldb = create uo_database
ldb.DBMS = 'MSS'
ldb.ServerName = '127.0.0.1,18433'
ldb.Database = 'tempdb'
ldb.LogId = 'sa'
ldb.LogPass = 'Zkkt@667788'
ldb.AutoCommit = true
// 关键坑:CreatePool 的模板连接必须是【已经 Open 成功】的连接。
// 只 create 不 Open,DLL 会打印 "template db: 00000000",池根本没建起来,
// 后面 QueryPool 一律返回 "not find name [池名] in pool"
if not ldb.Open() then
MessageBox('连接池', 'POOL_TPL_CONNECT_FAIL ' + ldb.SQLErrText)
destroy ldb
return
end if
lpool = create uo_database_pool
li_ret = lpool.CreatePool('mss_demo', ldb)
ls_ret = 'CREATE_POOL ret=' + String(li_ret)
ldb2 = lpool.QueryPool('mss_demo') // 从池中申请一条连接
if IsValid(ldb2) then
ls_ret = ls_ret + ' QUERY_POOL isopen=' + String(ldb2.IsOpen()) + ' alive=' + String(ldb2.IsAlive())
lrs = ldb2.ExecuteSQL('select count(*) as c from dbo.t_pbidea_demo')
if lrs.HasResultSet() then
if lrs.FetchNext() then
la_cnt = lrs.GetValue('c')
ls_ret = ls_ret + ' cnt=' + String(la_cnt)
end if
end if
lpool.GiveBackPool(ldb2) // 用完一定要还
ls_ret = ls_ret + ' GIVEN_BACK'
else
ls_ret = ls_ret + ' QUERY_POOL_FAIL'
end if
lpool.DestroyPool() // 销毁池,释放池中全部连接
ls_ret = ls_ret + ' DESTROYED'
destroy lpool
ldb.Close()
destroy ldb
MessageBox('连接池结果', ls_ret)
实测输出:CREATE_POOL ret=1 QUERY_POOL isopen=true alive=true cnt=4 GIVEN_BACK DESTROYED。
池的注册是 DLL 级别按名字的:同一个名字在任何地方 QueryPool 都能取到(PBIDEA 自带的 SQL 工作台就是 CreatePool 之后立刻 destroy pool,后面另建 uo_database_pool 照样能取)。这也意味着程序启动时最好先 DestroyPool() 清一次,避免上一轮残留。
九、连接配置复用
不想每次都写一堆赋值,就先连一次再存盘:
// 连成功后把属性存成配置文件,以后 Open(文件) 直连
uo_database ldb
ldb = create uo_database
ldb.DBMS = 'MSS'
ldb.ServerName = '127.0.0.1,18433'
ldb.Database = 'tempdb'
ldb.LogId = 'sa'
ldb.LogPass = 'Zkkt@667788'
if ldb.Open() then
ldb.SaveConfig('db.cfg') // 存盘
ldb.Close()
end if
destroy ldb
// 以后:
uo_database ldb2
ldb2 = create uo_database
if ldb2.Open('db.cfg') then
MessageBox('配置连接', 'OK db=' + ldb2.Database)
end if
destroy ldb2
十、坑清单(全部实机踩过)
#坑症状解法
1占位符写成 ?「至少一个参数没有被指定值」+ invalid index 1..n改成 :1 :2 或 :名字
2变参直接写字面量unknwn param!!!,字段被插成 NULL先声明类型化变量再传
3用 decimal 绑定unknwn param!!!,列不允许 Null改用 double
4CreatePool 传未 Open 的连接template db: 00000000、not find name in pool模板连接必须先 Open() 成功
5结果集读完再 FetchJsons返回 n=0重新 ExecuteSQL 拿新结果集
6CommitTrans() 后读行数RowsAffected() = -1提交前读,或读 lrs.RowsAffected()
7PB 字符串里双写单引号 ''C0031 语法错误外层改双引号,或用 ~'
8库列表只挂 sql.pblC0001: Illegal data type: uo_jsonwebsuite.pbl 一起挂
9池连接闲置 10 分钟自动断开,再用报错用前 IsAlive() 探活
10uo_field.GetDeciaml() 拼写写成 GetDecimal 编译不过照抄源码拼写 GetDeciaml
11MSSQL 绑 BLOB绑定失败加 DBParm:UseStreamForLongOrLobParameters="0";ICommandPrepare="SetParameterInfo"
12Oracle 连接报线程安全错设 ThreadSafe="Yes"
十一、对比:三条连库路线怎么选
维度SQLCA(PB 原生)uo_database(PBIDEA)pblit LIT *
归属PB 自带PBIDEA sql.pbl,需 PbIdea.dllpbnpm 的 pblit 驱动
多实例需自己声明 transactioncreate uo_database 随手开同左
参数化原生动态 SQL 四步式:1 变参 / BindParam依赖驱动
连接池无uo_database_pool 原生支持无
依赖无PbIdea.dll + sql.pbl + websuite.pblpblit{版本}.dll
适用最简 Demo / 老工程新项目推荐想绕开外部依赖时
顺带一提,uo_database 的 DBMS 也支持 "SQLite",如果你只是想本地存点数据、不想牵扯 SQL Server,把 DBMS 换成 SQLite、Database 换成 db 文件路径即可,其余代码一行不改。
十二、扩展点
[*]多库切换:按名字建多个池(CreatePool('ora', ldbOra)、CreatePool('mss', ldbMss)),业务代码用池名取连接,切换数据源只改建池那一处。
[*]配合 uo_json:FetchJsons 直接产出 uo_json,可以无缝接给 uo_httpclient 发走,或 WriteJsonFile 落盘,做接口/导出很省事。
[*]配合 uo_database 的事件:beginconnect / endconnect / ondisconnect 里挂日志,就能不侵入业务地做连接监控。
[*]分页:SQL Server 2008 用 ROW_NUMBER() 包一层即可,例如
select * from (select ROW_NUMBER() over(order by id) as rn, * from dbo.t_pbidea_demo) t where rn between 1 and 10。
实机验证情况(本文代码已全部跑通)
项结果
编译环境PowerBuilder 12.5,pypower pbl import 导入 nvo_dbpool_demo.sru(GBK/CRLF/无 BOM)
全量编译build rebuild --type full → 0 错误
运行验证PBVM 真机 pypower test → PASS,输出 DDL_OK / INS1 rows=1 INS2 rows=1 INS3 rows=1 / QUERY rows=3 / JSONS n=1 / SCALAR count=3 / TX_COMMIT rows=1 / CREATE_POOL ret=1 ... cnt=4 / DROP_END_OK
数据库SQL Server 2008 R2 SP3(10.50.6000.34),127.0.0.1,18433
编码检查check_enc.py bad=0;check_pb125.py 0 错误 0 提醒
PB10 与 PB12.5四个对象(uo_database / uo_recordset / uo_field / uo_database_pool)两版源码逐字节相同,API 无差异,示例以 PB10 为基准书写,PB12.5 直接复用
第九节 SaveConfig 那段为静态核对(配置文件路径按你的部署目录调整),其余全部为实机跑通。 PB10 兼容版 PBL 已打包上传(附件可直接下载)
对应文章:PBIDEA:用 uo_database 与 uo_database_pool 做数据库连接与连接池复用
一、环境要求
1. PowerBuilder 10 及以上(本包按 PB10 编译;PB12.5 实测同源码同行为,可直接复用);
2. Windows 7+,需部署与 PB 版本匹配的 PbIdea.dll / Pbidea_cs.dll(放到 exe 同目录);
3. 数据库:SQL Server 2008 R2(本文实测 127.0.0.1,18433,sa 登录);
uo_database 换 DBMS 取值即可连 ODBC / Oracle / MySQL / PostgreSQL / SQLite / DB2 / Sybase 等。
二、包内有什么
dbpool.pbt + dbpool.pbl(已编译,含 nvo_dbpool_demo / uo_database / uo_recordset / uo_field / uo_database_pool / uo_json 及依赖链)+ src 源码 + 使用说明.txt。
三、操作细节步骤
1. 解压到不含中文和空格的目录,例如 D:\pb10_dbpool;
2. 把 PbIdea.dll、Pbidea_cs.dll 复制进去;
3. PB10 打开 dbpool.pbt,Full Rebuild(应 0 错误,本包已按此标准生成);
4. 改 nvo_dbpool_demo 的 type variables:is_server / is_db / is_user / is_pwd;
5. 窗口按钮 clicked 里写:
nvo_dbpool_demo lnvo
lnvo = create nvo_dbpool_demo
lnvo.of_demo()
destroy lnvo
6. 运行点击按钮,MessageBox 输出每一步结果。
四、建表 SQL(示例依赖,对象运行时也会自己建、跑完自己删)
if object_id('dbo.t_pbidea_demo') is not null drop table dbo.t_pbidea_demo
go
create table dbo.t_pbidea_demo(
id int identity(1,1) primary key,
code varchar(20) not null,
name nvarchar(50)not null,
qty int not null default 0,
price decimal(12,2) not null default 0,
crt_time datetime not null default getdate()
)
go
默认写在 tempdb,不需要额外建库。
五、实机验证情况
· PB10:pypower --pb-version 100 全量编译 0 错误,pbl list 核对对象齐全;
· PB12.5:import → rebuild full 0 错误 → PBVM 真机 pypower test PASS,实际输出:
DDL_OK / INS1 rows=1 INS2 rows=1 INS3 rows=1 / QUERY rows=3 / JSONS n=1 / SCALAR count=3 /
TX_COMMIT rows=1 / CREATE_POOL ret=1 ... cnt=4 GIVEN_BACK DESTROYED / DROP_END_OK
· 数据库:SQL Server 2008 R2 SP3 (10.50.6000.34);编码 GBK/CRLF/无 BOM 全通过;
· 未实测:文中「SaveConfig 配置复用」一段为静态核对(配置文件路径需按你的部署目录调整),其余全部实机跑通。
六、三个最容易踩的坑(实机踩出来的)
1. 占位符是 :1 / :名字,不是 ? —— 写 ? 会报「至少一个参数没有被指定值」;
2. 变参位置不能直接写字面量,金额也不能用 decimal —— 先声明类型化变量,金额用 double,否则报 unknwn param!!!;
3. CreatePool 的模板连接必须已经 Open() 成功 —— 否则 DLL 打印 template db: 00000000,后面 QueryPool 一律 not find name in pool。
页:
[1]