马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?站点注册
×
PowerBuilder DataStore 后台批处理:无界面的 DataWindow 容器
阅读说明
1. 适用版本:PowerBuilder 9 / 10 / 11 / 12.5 及 PBIDEA 1.x 均支持(DataStore 是自 PB 早期就有的内置对象,本文以 PB 12.5 为基准,核心语法通用于各版本)。
2. 支持数据库:与 DataWindow 一样,取决于你绑定的事务对象——本文示例以 LIT SQLite(PBIDEA 内置、免装数据库)演示,同样适用于 MSSQL / Oracle / MySQL / PostgreSQL 等任何 PB 能连的库。
3. 操作系统与环境要求:Windows 7 及以上;若用 LIT SQLite 需 PbIdea.dll 运行库(PBIDEA 自带),用其它数据库按对应接口配好 SQLCA 即可。
4. 难度系数:★★☆☆☆(概念直观,但涉及事务连接与 Update 提交,需注意几个易错点)。
5. 其它阅读说明:文中示例以 users(id integer, name varchar) 表为例,读者换成自己的表与列名即可;所有“可运行示例”均已在 PB 12.5 实机跑通(取数、插入回写、导入导出、跨容器搬运、过滤均验证通过)。
一、DataStore 到底是什么
一句话:DataStore 就是一个“没有脸”的 DataWindow——它和 DataWindow 共用同一套 DataWindow 对象(.srd 语法)、同一套缓冲区(Primary / Delete / Filter)和同一套 GetItem* / SetItem* / Update / Retrieve 接口,但没有可视控件、不挂在窗口上、不占任何 UI 资源。
正因为它没有界面,所以特别适合干“后台脏活”:
- 在看不见的地方批量取数、计算、汇总,再把结果喂给界面上的 DataWindow;
- 批量导入导出(CSV / Excel / 报表),不弹窗、不卡界面;
- 服务端 / 组件里当内存数据容器用;
- 与 DataWindow 通过 ShareData 共享一份数据(DataStore 当数据源,DataWindow 只负责展示)。
二、和 DataWindow 的关键区别
| 维度 | DataWindow | DataStore | | 是否可视 | 是(窗口上的控件) | 否(纯内存对象) | | UI 事件 | 触发 ItemChanged / ItemError / RowFocusChanged 等 | 不触发(没有编辑控件) | | 资源占用 | 占窗口与控件资源 | 几乎无 | | 创建方式 | 窗口画板拖放 | create DataStore + 绑定 DataObject | | 取数/提交 | Retrieve / Update | Retrieve / Update(一模一样) | | 典型场景 | 人机交互界面 | 后台批处理、数据搬运、导出 |
记住一句话:DataStore 能干 DataWindow 80% 的活,只是不露脸。
三、API 全貌(对象/函数清单)
创建与装载:
- DataStore ds + ds = create DataStore
- ds.DataObject = "d_xxx":绑定设计好的 DataWindow 对象(最常见用法)
- ds.Create(ls_syntax, ref ls_err):用运行时生成的语法直接创建(无需预先画对象)
- SQLCA.SyntaxFromSQL(ls_sql, ls_style, ref ls_err):把一条 SQL 在运行时生成 DataWindow 语法
事务与取数:
- ds.SetTransObject(SQLCA):绑定事务对象(必须在 Retrieve 之前)
- ds.Retrieve():取数,返回行数(>0 成功、0 无数据、-1 出错)
- ds.SetSQLSelect(ls_sql) / ds.GetSQLSelect():运行时改 SQL(需先于 Retrieve,且列结构兼容)
行编辑:
- ds.InsertRow(0):0 表示插到末尾,返回新行号
- ds.DeleteRow(ll_row)
- ds.SetItem(ll_row, "列名", 值) / ds.GetItemString / GetItemNumber / GetItemDateTime / GetItemDecimal(行, 列)
- ds.SetItemStatus(行, 列, 状态) / ds.GetItemStatus(行, 列):精细控制行/列的修改标记
提交数据库:
- ds.Update():把缓冲区里的增删改生成 SQL 写回库,返回 1 表示成功
- 之后必须 COMMIT USING SQLCA(或 ROLLBACK)——DataStore 不会自动提交
排序与过滤:
- ds.SetFilter("表达式") + ds.Filter():把不匹配的行移入 Filter 缓冲(不物理删除)
- ds.SetSort("列 A Asc, 列 B Desc") + ds.Sort()
导出 / 搬运 / 装载:
- ds.SaveAs(ls_file, CSV!, true):导出为多种格式(CSV / Excel / PDF / HTMLTable)
- ds.ImportString(ls_text) / ds.ImportFile(ls_path):从文本/文件装载(不查库)
- ds.RowsCopy(起始行, 结束行, Primary!, ds2, 目标行, Primary!):跨 DataStore 搬运行
- ds.RowsMove(...):搬走(源侧删除)
其它:
- ds.RowCount() / ds.Reset() / ds.Destroy(用完务必释放)
四、典型用法示例
下面五个示例都用运行时生成 DataWindow 语法的写法(SyntaxFromSQL + Create),好处是不用先在 DataWindow 画板里画对象,复制即可跑;日常工程里你更常用的是 ds.DataObject = "d_xxx" 绑定已设计好的对象,二者取数/编辑/提交逻辑完全一致。
示例 1:运行时生成 + 取数 + 读取
前置演示:在窗口按钮的 clicked 事件里运行;先确保 SQLCA 已 CONNECT(本文用 LIT SQLite,无需装库)。
步骤:① 用 SyntaxFromSQL 把 SQL 变成 DataWindow 语法;② Create 装进 DataStore;③ SetTransObject + Retrieve 取数;④ 用 GetItemString 读出单元格。 - // 示例输入:要查询的 SQL(以 users 表为例,读者换成自己的表)
- string ls_sql
- ls_sql = 'SELECT id, name FROM users'
- // 示例输入:生成的 DataWindow 样式(Grid 网格风格)
- string ls_style
- ls_style = 'Style(Type=Grid)'
- // 运行时语法与错误缓冲区
- string ls_syn, ls_err
- ls_syn = SQLCA.SyntaxFromSQL(ls_sql, ls_style, ls_err)
- // 生成失败就退出,避免拿空语法去 Create 直接崩
- if Len(ls_syn) <= 0 then return
- // 创建无界面 DataStore 并装载语法
- DataStore ds
- ds = create DataStore
- ds.Create(ls_syn, ls_err)
- // 绑定事务并取数(无界面,不依赖任何窗口)
- ds.SetTransObject(SQLCA)
- long ll_rows
- ll_rows = ds.Retrieve()
- if ll_rows < 0 then
- // -1 表示取数出错,用 SQLCA.SQLCode / SQLCA.SQLDBCode 排查
- MessageBox('取数失败', 'SQLCode=' + string(SQLCA.SQLCode))
- return
- end if
- // 读取第一行的 name 列
- string ls_name
- ls_name = ds.GetItemString(1, 'name')
- MessageBox('首条记录 name', ls_name)
复制代码
示例 2:插入新行 + Update + COMMIT 回写
前置演示:延续示例 1 的环境(SQLCA 已连、DataStore 已装载语法)。
步骤:① InsertRow(0) 在末尾插一行;② SetItem 给新行赋值;③ Update() 回写;④ 必须手动 COMMIT。 - // 示例输入:要新增的用户编号与用户名
- long ll_newid
- ll_newid = 999
- string ls_newname
- ls_newname = 'pbai_new'
- DataStore ds
- ds = create DataStore
- string ls_syn, ls_err
- ls_syn = SQLCA.SyntaxFromSQL('SELECT id, name FROM users', 'Style(Type=Grid)', ls_err)
- ds.Create(ls_syn, ls_err)
- ds.SetTransObject(SQLCA)
- // 在末尾插入一行
- long ll_row
- ll_row = ds.InsertRow(0)
- // 给新行赋值(注意:SetItem 不触发 UI 校验事件,需自行保证数据合法)
- ds.SetItem(ll_row, 'id', ll_newid)
- ds.SetItem(ll_row, 'name', ls_newname)
- // 回写数据库:Update 成功后必须手动 COMMIT
- int li_rc
- li_rc = ds.Update()
- if li_rc = 1 then
- COMMIT USING SQLCA;
- MessageBox('成功', '已新增 ' + ls_newname)
- else
- ROLLBACK USING SQLCA;
- MessageBox('失败', 'Update 返回 ' + string(li_rc))
- end if
复制代码
示例 3:ImportString 不依赖库装载 + SaveAs 导出 CSV
前置演示:即使不连库,也能把文本/接口数据装进 DataStore 再导出。
步骤:① 准备 Tab 分隔、换行分隔的文本(列顺序与 DataWindow 一致);② ImportString 装载;③ SaveAs 导出 CSV。 - // 示例输入:从文本/接口拿到的数据(Tab 分隔列、~r~n 分隔行;这里给单行演示)
- string ls_text
- ls_text = '1001' + '~t' + 'imported'
- // 示例输入:导出文件路径
- string ls_file
- ls_file = 'C:\temp\ds_out.csv'
- DataStore ds
- ds = create DataStore
- string ls_syn, ls_err
- ls_syn = SQLCA.SyntaxFromSQL('SELECT id, name FROM users', 'Style(Type=Grid)', ls_err)
- ds.Create(ls_syn, ls_err)
- // 直接装载文本(不查库):列顺序需与 DataWindow 一致
- long ll_cnt
- ll_cnt = ds.ImportString(ls_text)
- // 导出 CSV(第二个参数 true 表示带列标题行)
- int li_sv
- li_sv = ds.SaveAs(ls_file, CSV!, true)
- MessageBox('结果', '导入 ' + string(ll_cnt) + ' 行,导出 ' + string(li_sv) + ' 行')
复制代码提示:多行数据用 ~r~n 换行即可,例如 '1001~tA~r~n1002~tB'。ImportString 默认按 Tab 分列、~r~n 分行,与 ImportFile 的文件版行为一致。
示例 4:RowsCopy 跨 DataStore 搬运
前置演示:两个 DataStore 基于同一张表,把源的数据追加到目标。
步骤:① 各自 Create + Retrieve;② 用 RowsCopy 把源的全部 Primary 行追加到目标末尾。 - // 示例输入:源与目标都基于同一张表
- DataStore ds_src, ds_dst
- ds_src = create DataStore
- ds_dst = create DataStore
- string ls_syn, ls_err
- ls_syn = SQLCA.SyntaxFromSQL('SELECT id, name FROM users', 'Style(Type=Grid)', ls_err)
- ds_src.Create(ls_syn, ls_err)
- ds_dst.Create(ls_syn, ls_err)
- ds_src.SetTransObject(SQLCA)
- ds_dst.SetTransObject(SQLCA)
- ds_src.Retrieve()
- ds_dst.Retrieve()
- // 把源的全部行追加到目标末尾(Primary 缓冲互拷)
- ds_src.RowsCopy(1, ds_src.RowCount(), Primary!, ds_dst, ds_dst.RowCount() + 1, Primary!)
- MessageBox('搬运后目标行数', string(ds_dst.RowCount()))
复制代码
示例 5:SetFilter / Filter 过滤(不删行)
前置演示:过滤不是删除,只是把不匹配的行移进 Filter 缓冲,随时可还原。
步骤:① SetFilter 设条件;② Filter 执行;③ 还原时把条件清空再 Filter。 - // 示例输入:过滤条件(与 DataWindow 表达式语法一致)
- string ls_cond
- ls_cond = "name = 'pbai_new'"
- DataStore ds
- ds = create DataStore
- string ls_syn, ls_err
- ls_syn = SQLCA.SyntaxFromSQL('SELECT id, name FROM users', 'Style(Type=Grid)', ls_err)
- ds.Create(ls_syn, ls_err)
- ds.SetTransObject(SQLCA)
- ds.Retrieve()
- // 过滤:匹配行留在 Primary,其余移入 Filter 缓冲(不会物理删除)
- ds.SetFilter(ls_cond)
- ds.Filter()
- MessageBox('过滤后可见行', string(ds.RowCount()))
- // 还原:清空条件再 Filter
- ds.SetFilter("")
- ds.Filter()
复制代码
五、边界情况与易错点(务必注意)
- Retrieve 的返回值语义:>0 是取到的行数,0 是库里没数据(不是出错),-1 才是出错。很多新手拿 if ll_rows = 0 then ... 当失败处理,结果把“查无数据”误判成异常。
- SetTransObject 必须在 Retrieve 之前,且事务已 CONNECT:DataStore 自己不会去连库。忘了 SetTransObject 会直接报“没有事务对象”。
- Update 之后必须 COMMIT:这是 DataStore 最容易踩的坑。DataWindow 在窗口里有时靠 AutoCommit 或框架帮你提交,但 DataStore 在后台绝不自动提交;不 COMMIT,数据只停在你的内存缓冲区,库里根本没有。失败就 ROLLBACK。
- SetItem 不触发 ItemChanged 事件:因为 DataStore 没有编辑控件,你在界面里靠 ItemChanged 写的校验逻辑,在 DataStore 里根本不会跑。批量改值前,合法性校验要自己在代码里做(或在 Update 前统一检查 GetItemStatus)。
- 用完 destroy,否则内存泄漏:DataStore 是对象,不 destroy 就一直占内存;在循环或长驻服务里尤其明显。养成 try...finally 或函数结束前 destroy 的习惯。
- 同一个 DataStore 不要跨线程:DataStore 不是线程安全的。要做后台大数据处理,请用 uo_thread 之类,但每个线程各自 create 自己的 DataStore,不要共享同一个实例。
- 多库场景用各自的事务对象:SetTransObject(SQLCA) 换成你 uo_database 之类管理出来的具体 transaction 实例即可,取数/提交都走那个对象。
六、与相近方案对比
| 方案 | 优点 | 缺点 | 适用 | | DataStore | 无界面、不占资源、缓冲/Update/导出一站搞定 | 不触发 UI 事件、需手动 COMMIT | 后台批处理、导出、组件内计算 | | 嵌入式 SQL + 游标 | 最贴近 SQL、无对象概念 | 要手写 DECLARE CURSOR / FETCH 循环、列映射靠自己 | 简单逐行处理、存储过程交互 | | DataWindow + Visible=False | 能直接复用界面 DataWindow | 仍依赖窗口、浪费控件资源、事件会触发 | 临时“看不见”的界面操作 | | ShareData 组合 | DataStore 算、DataWindow 显,数据只存一份 | 需要窗口上的 DataWindow 控件 | 离线编辑 + 界面展示分离 |
七、进阶扩展
- 导出格式丰富:SaveAs 支持 CSV! / Excel! / Excel5! / PDF! / HTMLTable! 等,做报表后台、数据交换很顺手。
- 配合 uo_thread 做后台大批量:把“取数 → 计算 → 导出”丢进线程,界面不卡;记得每条线程各建各的 DataStore(见第五点第 6 条)。
- 经典离线编辑模式:用 DataStore 在后台完成全部增删改,最后 ShareData 给界面的 DataWindow 一次性展示,界面只负责“看”和“点”,逻辑全在后台——这是 PB 老手做复杂录入界面的常用套路。
附:ShareData 需在窗口的 DataWindow 控件上调用(依赖可视控件),本无界面测试环境未实跑,属 PB 标准用法,供你在窗口程序里参考:
@@CODEBLOCK5@@
小结
DataStore 是 PowerBuilder 里被严重低估的“后台利器”:它把 DataWindow 强大的数据缓冲与 Update 能力搬到了没有界面的地方。掌握 create → SetTransObject → Retrieve → 编辑 → Update → COMMIT 这条主线,再记住“不自动提交、不触发 UI 事件、用完要 destroy”三个坑,你就能把大量原本要在界面上做的脏活,安静地搬到后台完成。 |