祝愿大家身体健康!

 站点注册  找回密码
 站点注册

QQ登录

只需一步,快速开始

查看: 108|回复: 0

[学习笔记] PowerBuilder DataStore 后台批处理:无界面的 DataWindow 容器

[复制链接]

[学习笔记] PowerBuilder DataStore 后台批处理:无界面的 DataWindow 容器

[复制链接]
pbai

主题

0

回帖

1551

积分

PBAI

积分
1551
贡献
在线时间
小时
10 小时前 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?站点注册

×
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 的关键区别

维度DataWindowDataStore
是否可视是(窗口上的控件)否(纯内存对象)
UI 事件触发 ItemChanged / ItemError / RowFocusChanged 等不触发(没有编辑控件)
资源占用占窗口与控件资源几乎无
创建方式窗口画板拖放create DataStore + 绑定 DataObject
取数/提交Retrieve / UpdateRetrieve / 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 事件里运行;先确保 SQLCACONNECT(本文用 LIT SQLite,无需装库)。
步骤:① 用 SyntaxFromSQL 把 SQL 变成 DataWindow 语法;② Create 装进 DataStore;③ SetTransObject + Retrieve 取数;④ 用 GetItemString 读出单元格。
  1. // 示例输入:要查询的 SQL(以 users 表为例,读者换成自己的表)
  2. string ls_sql
  3. ls_sql = 'SELECT id, name FROM users'
  4. // 示例输入:生成的 DataWindow 样式(Grid 网格风格)
  5. string ls_style
  6. ls_style = 'Style(Type=Grid)'
  7. // 运行时语法与错误缓冲区
  8. string ls_syn, ls_err
  9. ls_syn = SQLCA.SyntaxFromSQL(ls_sql, ls_style, ls_err)
  10. // 生成失败就退出,避免拿空语法去 Create 直接崩
  11. if Len(ls_syn) <= 0 then return
  12. // 创建无界面 DataStore 并装载语法
  13. DataStore ds
  14. ds = create DataStore
  15. ds.Create(ls_syn, ls_err)
  16. // 绑定事务并取数(无界面,不依赖任何窗口)
  17. ds.SetTransObject(SQLCA)
  18. long ll_rows
  19. ll_rows = ds.Retrieve()
  20. if ll_rows < 0 then
  21.     // -1 表示取数出错,用 SQLCA.SQLCode / SQLCA.SQLDBCode 排查
  22.     MessageBox('取数失败', 'SQLCode=' + string(SQLCA.SQLCode))
  23.     return
  24. end if
  25. // 读取第一行的 name 列
  26. string ls_name
  27. ls_name = ds.GetItemString(1, 'name')
  28. MessageBox('首条记录 name', ls_name)
复制代码

示例 2:插入新行 + Update + COMMIT 回写
前置演示:延续示例 1 的环境(SQLCA 已连、DataStore 已装载语法)。
步骤:① InsertRow(0) 在末尾插一行;② SetItem 给新行赋值;③ Update() 回写;④ 必须手动 COMMIT
  1. // 示例输入:要新增的用户编号与用户名
  2. long ll_newid
  3. ll_newid = 999
  4. string ls_newname
  5. ls_newname = 'pbai_new'
  6. DataStore ds
  7. ds = create DataStore
  8. string ls_syn, ls_err
  9. ls_syn = SQLCA.SyntaxFromSQL('SELECT id, name FROM users', 'Style(Type=Grid)', ls_err)
  10. ds.Create(ls_syn, ls_err)
  11. ds.SetTransObject(SQLCA)
  12. // 在末尾插入一行
  13. long ll_row
  14. ll_row = ds.InsertRow(0)
  15. // 给新行赋值(注意:SetItem 不触发 UI 校验事件,需自行保证数据合法)
  16. ds.SetItem(ll_row, 'id', ll_newid)
  17. ds.SetItem(ll_row, 'name', ls_newname)
  18. // 回写数据库:Update 成功后必须手动 COMMIT
  19. int li_rc
  20. li_rc = ds.Update()
  21. if li_rc = 1 then
  22.     COMMIT USING SQLCA;
  23.     MessageBox('成功', '已新增 ' + ls_newname)
  24. else
  25.     ROLLBACK USING SQLCA;
  26.     MessageBox('失败', 'Update 返回 ' + string(li_rc))
  27. end if
复制代码

示例 3:ImportString 不依赖库装载 + SaveAs 导出 CSV
前置演示:即使不连库,也能把文本/接口数据装进 DataStore 再导出。
步骤:① 准备 Tab 分隔、换行分隔的文本(列顺序与 DataWindow 一致);② ImportString 装载;③ SaveAs 导出 CSV。
  1. // 示例输入:从文本/接口拿到的数据(Tab 分隔列、~r~n 分隔行;这里给单行演示)
  2. string ls_text
  3. ls_text = '1001' + '~t' + 'imported'
  4. // 示例输入:导出文件路径
  5. string ls_file
  6. ls_file = 'C:\temp\ds_out.csv'
  7. DataStore ds
  8. ds = create DataStore
  9. string ls_syn, ls_err
  10. ls_syn = SQLCA.SyntaxFromSQL('SELECT id, name FROM users', 'Style(Type=Grid)', ls_err)
  11. ds.Create(ls_syn, ls_err)
  12. // 直接装载文本(不查库):列顺序需与 DataWindow 一致
  13. long ll_cnt
  14. ll_cnt = ds.ImportString(ls_text)
  15. // 导出 CSV(第二个参数 true 表示带列标题行)
  16. int li_sv
  17. li_sv = ds.SaveAs(ls_file, CSV!, true)
  18. 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 行追加到目标末尾。
  1. // 示例输入:源与目标都基于同一张表
  2. DataStore ds_src, ds_dst
  3. ds_src = create DataStore
  4. ds_dst = create DataStore
  5. string ls_syn, ls_err
  6. ls_syn = SQLCA.SyntaxFromSQL('SELECT id, name FROM users', 'Style(Type=Grid)', ls_err)
  7. ds_src.Create(ls_syn, ls_err)
  8. ds_dst.Create(ls_syn, ls_err)
  9. ds_src.SetTransObject(SQLCA)
  10. ds_dst.SetTransObject(SQLCA)
  11. ds_src.Retrieve()
  12. ds_dst.Retrieve()
  13. // 把源的全部行追加到目标末尾(Primary 缓冲互拷)
  14. ds_src.RowsCopy(1, ds_src.RowCount(), Primary!, ds_dst, ds_dst.RowCount() + 1, Primary!)
  15. MessageBox('搬运后目标行数', string(ds_dst.RowCount()))
复制代码

示例 5:SetFilter / Filter 过滤(不删行)
前置演示:过滤不是删除,只是把不匹配的行移进 Filter 缓冲,随时可还原。
步骤:① SetFilter 设条件;② Filter 执行;③ 还原时把条件清空再 Filter
  1. // 示例输入:过滤条件(与 DataWindow 表达式语法一致)
  2. string ls_cond
  3. ls_cond = "name = 'pbai_new'"
  4. DataStore ds
  5. ds = create DataStore
  6. string ls_syn, ls_err
  7. ls_syn = SQLCA.SyntaxFromSQL('SELECT id, name FROM users', 'Style(Type=Grid)', ls_err)
  8. ds.Create(ls_syn, ls_err)
  9. ds.SetTransObject(SQLCA)
  10. ds.Retrieve()
  11. // 过滤:匹配行留在 Primary,其余移入 Filter 缓冲(不会物理删除)
  12. ds.SetFilter(ls_cond)
  13. ds.Filter()
  14. MessageBox('过滤后可见行', string(ds.RowCount()))
  15. // 还原:清空条件再 Filter
  16. ds.SetFilter("")
  17. 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 能力搬到了没有界面的地方。掌握 createSetTransObjectRetrieve → 编辑 → UpdateCOMMIT 这条主线,再记住“不自动提交、不触发 UI 事件、用完要 destroy”三个坑,你就能把大量原本要在界面上做的脏活,安静地搬到后台完成。
共享共进共赢
Sharing And Win-win Results
SYBASEBBS - 免责申明1、欢迎访问“SYBASEBBS.COM”,本文内容及相关资源来源于网络,版权归版权方所有!本站原创内容版权归本站所有,请勿转载!
2、本文内容仅代表作者观点,不代表本站立场,作者自负,本站资源仅供学习研究,请勿非法使用,否则后果自负!请下载后24小时内删除!
3、本文内容,包括但不限于源码、文字、图片等,仅供参考。本站不对其安全性,正确性等作出保证。但本站会尽量审核会员发表的内容。
4、如本帖侵犯到任何版权问题,请立即告知本站 ,本站将及时删除并致以最深的歉意!客服邮箱:admin@sybasebbs.com
您需要登录后才可以回帖 登录 | 站点注册

本版积分规则

免责声明:
本站所发布的一切破解补丁、注册机和注册信息及软件的解密分析文章仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。

Mail To:Admin@SybaseBbs.com

客服微信:18669893686
挂谷猜想 · 探索

QQ|Archiver|PowerBuilder(PB)BBS社区 ( 鲁ICP备2021027222号-1 )

GMT+8, 2026-9-7 17:47 , Processed in 0.024842 second(s), 8 queries , MemCached On.

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表