Files
thinkyu/Doc/GridView改用ObjectDataSource計畫.md
sryang 439e941268 feat: fmSendSalaryStrip GridView 改用 ObjectDataSource
- SsmsDAC 新增 SelectSalaryStrip/SelectSalaryStripSum

- dsSSMS 改 ObjectDataSource;dsSSMSSum 移除,footer 改用 SsmsDAC
2026-09-10 11:04:33 +08:00

14 KiB
Raw Permalink Blame History

GridView 銜接的 SqlDataSource 改用 ObjectDataSource

分支:feature/gridview-to-objectdatasource

目標與原則

  • 只改DataSourceID 綁定 SqlDataSource 的 GridView;檔內的 FormView / DataList / DropDownList 之 SqlDataSource 不動。
  • 缺的 DAC 建立專屬 DAC;查詢方法只要 SelectPageSelectCount需要 Insert / Update / Delete。
  • 沿用既有 ObjectDataSource 工法(參考 fmRpir / RpirDAC):
    • ObjectDataSource 設定 TypeNameSelectMethodSelectCountMethodEnablePaging="true"
    • SelectPage 前兩個參數為 int startRowIndex, int maximumRows(由 ObjectDataSource 自動帶入)。
    • 動態篩選邏輯由 code-behind 的 SelectCommand 字串組裝,改為 DAC 內參數化查詢,code-behind 只設 SelectParameters[...].DefaultValue
    • 事件簽章由 SqlDataSourceStatusEventArgs 改為 ObjectDataSourceStatusEventArgs

盤點:GridView ↔ SqlDataSource(共 24 個 / 19 檔,皆在 Web 專案)

# 檔案 GridView ID SqlDataSource ID 來源表 既有 DAC 狀態
1 forms/customer/fmBcus.ascx gvBCUS dsBCUSGrid BCUS 無→BcusDAC 完成
2 forms/customer/fmBpaa.ascx gvBPAA dsBPAAGrid BPAA BpaaDAC(+Select) 完成
3 forms/customer/fmBpab.ascx gvBPAB dsBPABGrid BPAB 無→BpabDAC 完成
4 forms/customer/fmBpabBig.ascx gvBPAB dsBPABGrid BPAB 無→BpabDAC 完成
5 forms/customer/fmBpabSmall.ascx gvBPAB dsBPABGrid BPAB 無→BpabDAC 完成
6 forms/customer/fmBpja.ascx gvBPJA dsBPJAGrid BPJA 無→BpjaDAC 完成
7 forms/customer/fmBpjb.ascx gvBPJB dsBPJBGrid BPJB 無→BpjbDAC 完成
8 forms/customer/fmBprj.ascx gvBPRJ dsBPRJGrid BPRJ BprjDAC(+Select) 完成
9 forms/customer/fmBprm.ascx gvBPRM dsBPRMGrid BPRM BprmDAC(+Select) 完成
10 forms/customer/fmRasn.ascx gvRASN_RAENO dsRASN_RAENO1 RASN+BPSNView RasnDAC(+Select) 完成
11 forms/customer/fmRasn.ascx gvRASN_RABRH dsRASN_RABRH1 RASN+BCLS RasnDAC(+Select) 完成
12 forms/customer/fmRasn.ascx RASNGrid dsRASNGrid RASN/BPJB/BPSNView RasnDAC(+Select) 完成
13 forms/customer/fmRpfe.ascx RPFEGrid dsRPFEGrid RPFE 無→RpfeDAC 完成
14 forms/flow/fmSign.ascx gvRPAB2 dsRPAB2 RPAB RpabDAC(+Select) 完成
15 forms/flow/fmSign.ascx gvRPAB dsRPAB RPAB RpabDAC(+Select) 完成
16 forms/summary/fmSalaryCalc.ascx GridView1 dsSACD SACD 無→SacdDAC 完成
17 forms/worker/fmBpsn.ascx GridView1 dsBPSNGrid BPSNView BpsnDAC 進行中
18 forms/worker/fmRowk.ascx gvROWK dsROWKGrid ROWK 保留(使用 WUserControlBase 的 SqlDataSource 框架,需另案)
19 forms/worker/fmRpar.ascx RPARGrid dsRPARGrid RPAR RparDAC(+Select) 完成
20 forms/worker/fmRparWorker.ascx RPABGrid dsRPAB RPAB RpabDAC(+Select) 完成
21 forms/worker/fmRparWorker.ascx RPARGrid dsRPARGrid RPAR RparDAC(+Select) 完成
22 forms/worker/fmRsaj.ascx RSAJGrid dsRSAJGrid RSAJ RsajDAC(+Select) 完成
23 forms/worker/fmSendSalaryStrip.ascx gvSSMS dsSSMS SSMS SsmsDAC(+Select) 完成
24 forms/worker/fmSendSalaryStrip.ascx gvSSMSSum(已註解) dsSSMSSum SSMS SsmsDAC 完成(移除 dsSSMSSumfooter 改用 SsmsDAC

已為 ObjectDataSource(不列入):fmBPAPdsBPAP)、fmRparManagerdsRpad)、fmSign 內 dsRPAR未簽/已簽/我的/我的督導、fmBpslfmBPSEfmRpir

驗證樣板:fmBPSN

現況

  • fmBpsn.ascx GridView1DataSourceID="dsBPSNGrid"DataKeyNames="BPENO"AllowPaging="True",事件 OnDataBound / OnPageIndexChanged / OnRowDataBound / OnSelectedIndexChanged
  • dsBPSNGridSelectCommand="SELECT * FROM BPSNView WHERE 1=0"OnSelected="dsBPSNGrid_Selected"
  • fmBpsn.ascx.csSearch()select/from/where 欄位 + AddSearch(...) 動態組 SQL 後塞入 dsBPSNGrid.SelectCommand
  • 篩選條件:BPENO/BPNME/BPENM/BPIDNLIKE)、BPEDY_TYPE(在職/離職)、BPDPT_TYPE(部門/專案,PublicVariable.InnerDepartment)、BPSDY/BPEDY 區間、非管理者排除 00001/00002/00003!canSearch 時只查自己。
  • 另有「依 BPENO 跳到該員工所在頁」邏輯(用 ROWID - 1 反查)。

變更內容

  1. Web/App_Code/DAC/BpsnDAC.cs:新增
    • SelectPage(int startRowIndex, int maximumRows, string BPENO, string BPNME, string BPENM, string BPIDN, string BPEDY_TYPE, string BPDPT_TYPE, string BPSDY1, string BPSDY2, string BPEDY1, string BPEDY2, bool canSearch, string userId)
    • SelectCount(同上,去掉 startRowIndex/maximumRows)
    • SelectBPENOIndex(string targetBPENO, 同 SelectCount 參數)(保留自動跳頁)
  2. Web/forms/worker/fmBpsn.ascxdsBPSNGrid 由 SqlDataSource 改為 ObjectDataSourceTypeName="BpsnDAC"SelectMethod="SelectPage"SelectCountMethod="SelectCount"EnablePaging="true")。
  3. Web/forms/worker/fmBpsn.ascx.csSearch() 改設 SelectParametersdsBPSNGrid_SelectedObjectDataSourceStatusEventArgsOnInit 移除 ConnectionString 設定;移除不再使用的 select/from/where 欄位。

驗證步驟

  • aspnet_compiler -v / -p "D:\VS.NET\Thinkyu\Web"(或 VS 開站台)確認 App_Code 編譯無誤。
  • 手動測試:各篩選條件、在職/離職、部門/專案、分頁、SelectedIndex、無資料 NODATA、由 RPIR 帶 BPENO 進入時自動跳頁。
  • 核對 Grid 回傳欄位與舊 SQL 一致(RAPYN/RAPYNX/RABRH/RABRHX/RAJNO/RAJNOX/RACNO/RACNOX/RPCPY)。

效能比較(fmBPSN:舊 SqlDataSource vs 新 ObjectDataSource

方法

正式資料庫(192.168.1.50:23450)不可達,改用本機 SQL Server LocalDB 建立代表性 schemaBPSNView + RASN/BPAA/BCLS/BPJA/BPJB/RPIR/RPIB 與相關索引)模擬。基準程式為 C# + System.Data.SqlClient,比較:

  • 舊作法SqlDataSource 動態 SQL 全量查詢(GridView 端分頁)。
  • 新作法ObjectDataSource SelectPageTOP 10)+ SelectCountPageSize=10)。

取 7 次平均,SET STATISTICS IO 統計 logical reads。此為合成環境、無網路傳輸,絕對值與正式環境不同,比例可作參考。

結果(Filter=全部)

指標 N=20,000 舊 N=20,000 新(首頁) N=100,000 舊 N=100,000 新(首頁)
回傳/繫結列數 20,000 10 100,000 10
DB 查詢時間 758.0 ms 123.1 mscount 122.0 + page 1.1 4,289.6 ms 715.3 mscount 714.0 + page 1.3
logical reads 81,662 81,863 608,191 608,443
App 記憶體(DataTable ~54.6 MB ~0 ~252 MB ~0
  • 首頁 SelectPage 極快(1.11.3 ms、216267 reads):可用 BPENO 索引提早結束。
  • DB 成本幾乎全落在 SelectCount(與舊全量查詢相同 reads),因為 COUNT 仍需掃描同樣資料。
  • 真正的效能來源是 DB→App 的資料量由 N 列降為 10 列,省下網路傳輸與 DataTable/GridView 繫結;網路越慢、欄位越多,實務改善越大。
  • 深頁跳轉較差ROWID > @START 且 START 很大時,無法提早結束,N=100,000 時 page 需 2,351 ms / 2,621,036 reads(約舊作法的 4.3 倍 reads)。此即 fmBPSN「依 BPENO 自動跳頁」可能踩到的情境。

結論

  • 一般瀏覽(首頁、逐頁):大幅改善(列數 ~10,000×、記憶體趨近 0、DB 時間約 6×)。
  • 深頁跳轉:DB reads 反而增加;後續可評估限制跳頁深度、或改用 keyset 分頁優化。

分頁 SQL 形式比較(N=100,000、PageSize=10、7 次平均)

形式 page@0 ms page@0 reads page@deep ms page@deep reads
OLD(全量) 4,098.0 608,191 4,053.0 608,191
TOP(現行 SelectPage 7.4 273 2,166.3 2,621,045
BETWEEN @START+1 AND @START+@MAX(兩層查詢) 3.1 268 2,192.3 2,621,037
KEYSET:先在窄集合算 ROWID 再 join 明細 3.4 300 48.0 6,549
  • BETWEENTOP 幾乎相同:兩者都必須對「過濾後、已 join 的完整集合」計算 ROW_NUMBER();當 offset 很大時仍要處理前面所有資料列,故深頁成本相同。BETWEEN 不會改善。
  • KEYSET 形式:把 ROW_NUMBER() 只算在 BPSNView(篩選欄位全部屬於 BPSNView),外層再用 ROWID BETWEEN 取出該頁的 10 個 BPENO,之後才 join 明細表。深頁由 2,166 ms / 262 萬 reads 降為 48 ms / 6,549 reads(約 45× 快、400× reads
  • 限制KEYSET 假設各明細 join 為 1:1(特別是 RASN 的現職派遣),否則原本「以 join 後結果計數」的語意會不同(同一員工可能多列)。已於 1:1 合成資料驗證 TOPKEYSET 取出的頁面完全相同(差異 0 筆);正式環境需先確認 RASN 現職是否唯一。

最終實作(已套用於 BpsnDAC

  • SelectPage 改為 KEYSET:先在 BPSNView(套用篩選)算 ROW_NUMBER() 取出該頁 BPENO,再 join 明細;WHERE R.ROWID BETWEEN @STARTROWINDEX+1 AND @STARTROWINDEX+@MAXIMUMROWS
  • SelectCount 改為窄集合 SELECT COUNT(*) FROM BPSNView A WHERE 1=1 {filter}(與 KEYSET 分頁一致,且不需 join)。
  • SelectBPENOIndex 同步改為窄集合。
指標(N=100,000、PageSize=10 舊 SqlDataSource 新 KEYSET
SelectPage 首頁 4,098 ms / 608,191 reads(全量) 3.4 ms / 300 reads
SelectPage 深頁 4,053 ms / 608,191 reads 48 ms / 6,549 reads
SelectCount(新增) 110 ms / 6,253 reads
首頁總計(count + page 4,098 ms / 608,191 reads ~113 ms / ~6,553 reads
回傳/繫結列數 100,000 10
App 記憶體 ~252 MB ~0

改善:首頁總計約 38× 快、93× 少 reads;深頁約 45×。原本 count 需 608,176 reads,窄集合後降為 6,253 reads。

待辦清單(Todo

  • 建立分支 feature/gridview-to-objectdatasource
  • 撰寫本計畫文檔
  • fmBPSNBpsnDAC 新增 SelectPage / SelectCount / SelectBPENOIndex
  • fmBPSNfmBpsn.ascx 改 dsBPSNGrid 為 ObjectDataSource
  • fmBPSNfmBpsn.ascx.cs 調整 Search / Selected / OnInit
  • fmBPSN:編譯驗證(aspnet_compiler 通過,產出 App_Code.dll
  • fmBPSN:手動功能驗證(各篩選、分頁、跳頁、NODATA)
  • fmBcus:新增 BcusDACSelectPage/SelectCount/SelectBCNUMIndex);dsBCUSGrid 改 ObjectDataSourceSearch/Selected/DataBound 調整;編譯通過
  • 其餘 22 個 Grid 依樣板陸續轉換(逐檔 commit)

變更紀錄

日期 內容
2026-09-10 建立計畫文檔、盤點清單;開始 fmBPSN 驗證
2026-09-10 fmBPSN 轉換完成:BpsnDAC 新增 SelectPage/SelectCount/SelectBPENOIndexfmBpsn.ascx 改 ObjectDataSourcefmBpsn.ascx.cs 調整 Search/Selected/OnInitaspnet_compiler 編譯通過
2026-09-10 修復 BpsnDAC.cs 亂碼註解(原檔為 Big5,編輯後毀損),自 git HEAD 還原中文註解,並將檔案統一改存為 UTF-8 with BOM;重新編譯通過
2026-09-10 以 LocalDB 合成資料(2 萬/10 萬筆)基準測試舊 SqlDataSource 與新 ObjectDataSource;結果:首頁列數 ~10,000×、記憶體趨近 0、DB 時間約 6×;深頁跳轉 reads 反而增加
2026-09-10 分頁 SQL 形式比較:BETWEENTOP 相同(無改善);改採 KEYSET(窄集合算 ROWID 再 join 明細)並將 SelectCount/SelectBPENOIndex 改為窄集合。深頁 2,166ms/262萬 reads → 48ms/6,549 readscount 714ms/608,176 reads → 110ms/6,253 readsaspnet_compiler 編譯通過
2026-09-10 轉換 fmBcusBCUS):新增 BcusDACdsBCUSGrid 改 ObjectDataSource;跳回選取列改用 SelectBCNUMIndexaspnet_compiler 編譯通過
2026-09-10 轉換 fmBpaaBPAA):BpaaDAC 新增 SelectPage/SelectCount/SelectBPAAIndexdsBPAAGrid 改 ObjectDataSource;編譯通過
2026-09-10 轉換 fmBpab / fmBpabBig / fmBpabSmallBPAB):新增 BpabDACSelectPage/SelectCountmode 區分科目/大類/小類);三檔 dsBPABGrid 改 ObjectDataSource;編譯通過
2026-09-10 轉換 fmBpjaBPJA):新增 BpjaDACSelectPage/SelectCount/SelectBPJAIndex);dsBPJAGrid 改 ObjectDataSource;編譯通過
2026-09-10 轉換 fmBpjbBPJB):新增 BpjbDACSelectPage/SelectCount);dsBPJBGrid 改 ObjectDataSource;編譯通過
2026-09-10 轉換 fmBprjBPRJ):BprjDAC 新增 SelectPage/SelectCount/SelectBPRJIndex(含 LAST_YEAR 排序);dsBPRJGrid 改 ObjectDataSource;編譯通過
2026-09-10 轉換 fmBprmBPRM):BprmDAC 新增 SelectPage/SelectCountdsBPRMGrid 改 ObjectDataSourcedsBPRM_Selected 拆分為 grid/form 兩個事件;編譯通過
2026-09-10 轉換 fmRasnRASN3 個 Grid):RasnDAC 新增 SelectPage/SelectCount 及 SelectRAENO/SelectRABRH;三個 datasource 改 ObjectDataSourceSearch/Selected 調整;編譯通過
2026-09-10 轉換 fmRpfeRPFE):新增 RpfeDACSelectPage/SelectCount);dsRPFEGrid 改 ObjectDataSource;編譯通過
2026-09-10 轉換 fmSignRPAB):RpabDAC 新增 SelectRPABdsRPAB2/dsRPAB 改 ObjectDataSourcedsRPAB2_Selecting 改 ODS 事件;編譯通過
2026-09-10 轉換 fmSalaryCalcSACD):新增 SacdDACSelectByProject);dsSACD 改 ObjectDataSource;編譯通過
2026-09-10 轉換 fmRparRPAR):RparDAC 新增 SelectPage/SelectCountdsRPARGrid 改 ObjectDataSourceSearch/Selected 調整;編譯通過
2026-09-10 轉換 fmRparWorkerRPAR/RPAB):RparDAC 新增 SelectWorkerPage/SelectWorkerCountRpabDAC 新增 SelectRPABByRPRENdsRPARGrid/dsRPAB 改 ObjectDataSource;編譯通過
2026-09-10 轉換 fmRsajRSAJ):RsajDAC 新增 SelectPage/SelectCountdsRSAJGrid 改 ObjectDataSource;編譯通過
2026-09-10 轉換 fmSendSalaryStripSSMS):SsmsDAC 新增 SelectSalaryStrip/SelectSalaryStripSumdsSSMS 改 ObjectDataSource;移除 dsSSMSSumfooter 改用 SsmsDAC);編譯通過
2026-09-10 fmRowkROWK)保留未轉換:其 Grid 綁定使用 WUserControlBase 的 Grid_View/GridView_DataSourceSqlDataSource)框架與 override Search,無法直接改用 ObjectDataSource,需另案重構