From 9adbcfe0a447429dd6832af10e02538ac7d94434 Mon Sep 17 00:00:00 2001 From: Neo Yang Date: Thu, 10 Sep 2026 10:29:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20fmBPSN=20GridView=20=E6=94=B9=E7=94=A8?= =?UTF-8?q?=20ObjectDataSource=20=E4=B8=A6=E6=8E=A1=20KEYSET=20=E5=88=86?= =?UTF-8?q?=E9=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - BpsnDAC 新增 SelectPage/SelectCount/SelectBPENOIndex - SelectPage 採 KEYSET:先在窄集合 BPSNView 算 ROWID,再 join 該頁明細 - SelectCount/SelectBPENOIndex 改用窄集合(免 join) - fmBpsn.ascx dsBPSNGrid 改 ObjectDataSource(EnablePaging) - fmBpsn.ascx.cs 調整 Search/Selected/OnInit - 修復 BpsnDAC.cs 亂碼註解並改存 UTF-8 BOM - 新增 Doc/GridView改用ObjectDataSource計畫.md(盤點、計畫、效能比較) --- Doc/GridView改用ObjectDataSource計畫.md | 143 +++++++++++++ Web/App_Code/DAC/BpsnDAC.cs | 258 ++++++++++++++++++++++-- Web/forms/worker/fmBpsn.ascx | 25 ++- Web/forms/worker/fmBpsn.ascx.cs | 95 ++------- 4 files changed, 422 insertions(+), 99 deletions(-) create mode 100644 Doc/GridView改用ObjectDataSource計畫.md diff --git a/Doc/GridView改用ObjectDataSource計畫.md b/Doc/GridView改用ObjectDataSource計畫.md new file mode 100644 index 0000000..4630d7b --- /dev/null +++ b/Doc/GridView改用ObjectDataSource計畫.md @@ -0,0 +1,143 @@ +# GridView 銜接的 SqlDataSource 改用 ObjectDataSource + +分支:`feature/gridview-to-objectdatasource` + +## 目標與原則 + +- 只改**以 `DataSourceID` 綁定 SqlDataSource 的 GridView**;檔內的 FormView / DataList / DropDownList 之 SqlDataSource 不動。 +- 缺的 DAC 建立**專屬** DAC;查詢方法只要 `SelectPage` 與 `SelectCount`,**不**需要 Insert / Update / Delete。 +- 沿用既有 ObjectDataSource 工法(參考 `fmRpir` / `RpirDAC`): + - ObjectDataSource 設定 `TypeName`、`SelectMethod`、`SelectCountMethod`、`EnablePaging="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 | 無 | 待辦 | +| 2 | forms/customer/fmBpaa.ascx | gvBPAA | dsBPAAGrid | BPAA | BpaaDAC(無 Select) | 待辦 | +| 3 | forms/customer/fmBpab.ascx | gvBPAB | dsBPABGrid | BPAB | 無 | 待辦 | +| 4 | forms/customer/fmBpabBig.ascx | gvBPAB | dsBPABGrid | BPAB | 無 | 待辦 | +| 5 | forms/customer/fmBpabSmall.ascx | gvBPAB | dsBPABGrid | BPAB | 無 | 待辦 | +| 6 | forms/customer/fmBpja.ascx | gvBPJA | dsBPJAGrid | BPJA | 無 | 待辦 | +| 7 | forms/customer/fmBpjb.ascx | gvBPJB | dsBPJBGrid | BPJB | 無 | 待辦 | +| 8 | forms/customer/fmBprj.ascx | gvBPRJ | dsBPRJGrid | BPRJ | BprjDAC | 待辦 | +| 9 | forms/customer/fmBprm.ascx | gvBPRM | dsBPRMGrid | BPRM | BprmDAC | 待辦 | +| 10 | forms/customer/fmRasn.ascx | gvRASN_RAENO | dsRASN_RAENO1 | RASN+BPSNView | RasnDAC | 待辦 | +| 11 | forms/customer/fmRasn.ascx | gvRASN_RABRH | dsRASN_RABRH1 | RASN+BCLS | RasnDAC | 待辦 | +| 12 | forms/customer/fmRasn.ascx | RASNGrid | dsRASNGrid | RASN/BPJB/BPSNView | RasnDAC | 待辦 | +| 13 | forms/customer/fmRpfe.ascx | RPFEGrid | dsRPFEGrid | RPFE | 無 | 待辦 | +| 14 | forms/flow/fmSign.ascx | gvRPAB2 | dsRPAB2 | RPAB | RpabDAC | 待辦 | +| 15 | forms/flow/fmSign.ascx | gvRPAB | dsRPAB | RPAB | RpabDAC | 待辦 | +| 16 | forms/summary/fmSalaryCalc.ascx | GridView1 | dsSACD | SACD | 無 | 待辦 | +| 17 | forms/worker/fmBpsn.ascx | GridView1 | dsBPSNGrid | BPSNView | BpsnDAC | **進行中** | +| 18 | forms/worker/fmRowk.ascx | gvROWK | dsROWKGrid | ROWK | 無 | 待辦 | +| 19 | forms/worker/fmRpar.ascx | RPARGrid | dsRPARGrid | RPAR | RparDAC | 待辦 | +| 20 | forms/worker/fmRparWorker.ascx | RPABGrid | dsRPAB | RPAB | RpabDAC | 待辦 | +| 21 | forms/worker/fmRparWorker.ascx | RPARGrid | dsRPARGrid | RPAR | RparDAC | 待辦 | +| 22 | forms/worker/fmRsaj.ascx | RSAJGrid | dsRSAJGrid | RSAJ | RsajDAC | 待辦 | +| 23 | forms/worker/fmSendSalaryStrip.ascx | gvSSMS | dsSSMS | SSMS | SsmsDAC | 待辦 | +| 24 | forms/worker/fmSendSalaryStrip.ascx | gvSSMSSum | dsSSMSSum | SSMS | SsmsDAC | 待辦 | + +> 已為 ObjectDataSource(不列入):`fmBPAP`(dsBPAP)、`fmRparManager`(dsRpad)、`fmSign` 內 dsRPAR未簽/已簽/我的/我的督導、`fmBpsl`、`fmBPSE`、`fmRpir`。 + +## 驗證樣板:fmBPSN + +### 現況 +- `fmBpsn.ascx` GridView1:`DataSourceID="dsBPSNGrid"`、`DataKeyNames="BPENO"`、`AllowPaging="True"`,事件 `OnDataBound / OnPageIndexChanged / OnRowDataBound / OnSelectedIndexChanged`。 +- `dsBPSNGrid`:`SelectCommand="SELECT * FROM BPSNView WHERE 1=0"`、`OnSelected="dsBPSNGrid_Selected"`。 +- `fmBpsn.ascx.cs` 的 `Search()` 以 `select`/`from`/`where` 欄位 + `AddSearch(...)` 動態組 SQL 後塞入 `dsBPSNGrid.SelectCommand`。 +- 篩選條件:BPENO/BPNME/BPENM/BPIDN(LIKE)、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.ascx`:`dsBPSNGrid` 由 SqlDataSource 改為 ObjectDataSource(`TypeName="BpsnDAC"`、`SelectMethod="SelectPage"`、`SelectCountMethod="SelectCount"`、`EnablePaging="true"`)。 +3. `Web/forms/worker/fmBpsn.ascx.cs`:`Search()` 改設 SelectParameters;`dsBPSNGrid_Selected` 改 `ObjectDataSourceStatusEventArgs`;`OnInit` 移除 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 建立代表性 schema(`BPSNView` + `RASN/BPAA/BCLS/BPJA/BPJB/RPIR/RPIB` 與相關索引)模擬。基準程式為 C# + `System.Data.SqlClient`,比較: +- **舊作法**:SqlDataSource 動態 SQL 全量查詢(GridView 端分頁)。 +- **新作法**:ObjectDataSource `SelectPage`(`TOP 10`)+ `SelectCount`(`PageSize=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 ms(count 122.0 + page 1.1) | 4,289.6 ms | 715.3 ms(count 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.1~1.3 ms、216~267 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** | + +- **`BETWEEN` 與 `TOP` 幾乎相同**:兩者都必須對「過濾後、已 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 合成資料驗證 `TOP` 與 `KEYSET` 取出的頁面完全相同(差異 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) + +- [x] 建立分支 `feature/gridview-to-objectdatasource` +- [x] 撰寫本計畫文檔 +- [x] fmBPSN:BpsnDAC 新增 SelectPage / SelectCount / SelectBPENOIndex +- [x] fmBPSN:fmBpsn.ascx 改 dsBPSNGrid 為 ObjectDataSource +- [x] fmBPSN:fmBpsn.ascx.cs 調整 Search / Selected / OnInit +- [x] fmBPSN:編譯驗證(aspnet_compiler 通過,產出 App_Code.dll) +- [ ] fmBPSN:手動功能驗證(各篩選、分頁、跳頁、NODATA) +- [ ] 其餘 23 個 Grid 依樣板陸續轉換(逐檔 commit) + +## 變更紀錄 + +| 日期 | 內容 | +|---|---| +| 2026-09-10 | 建立計畫文檔、盤點清單;開始 fmBPSN 驗證 | +| 2026-09-10 | fmBPSN 轉換完成:BpsnDAC 新增 SelectPage/SelectCount/SelectBPENOIndex;fmBpsn.ascx 改 ObjectDataSource;fmBpsn.ascx.cs 調整 Search/Selected/OnInit;aspnet_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 形式比較:`BETWEEN` 與 `TOP` 相同(無改善);改採 KEYSET(窄集合算 ROWID 再 join 明細)並將 SelectCount/SelectBPENOIndex 改為窄集合。深頁 2,166ms/262萬 reads → 48ms/6,549 reads;count 714ms/608,176 reads → 110ms/6,253 reads;aspnet_compiler 編譯通過 | diff --git a/Web/App_Code/DAC/BpsnDAC.cs b/Web/App_Code/DAC/BpsnDAC.cs index 16932a5..62de008 100644 --- a/Web/App_Code/DAC/BpsnDAC.cs +++ b/Web/App_Code/DAC/BpsnDAC.cs @@ -1,14 +1,16 @@ -using System; +using System; using System.Data; using System.Data.Common; using System.Configuration; using System.Text; using System.Collections.Generic; +using System.ComponentModel; using Wellan.Data; /// -/// uƪƳBzO +/// 員工資料的資料處理類別 /// +[DataObject(true)] public class BpsnDAC : Wellan.Data.BPSN_DAC { private DbCommand cmdSelect; @@ -115,7 +117,7 @@ public class BpsnDAC : Wellan.Data.BPSN_DAC } /// - /// oHثenM + /// 取得人員目前派駐專案 /// /// /// @@ -125,7 +127,7 @@ public class BpsnDAC : Wellan.Data.BPSN_DAC } /// - /// oHثenM + /// 取得人員目前派駐專案 /// /// /// @@ -154,7 +156,7 @@ public class BpsnDAC : Wellan.Data.BPSN_DAC } /// - /// oHثenaI + /// 取得人員目前派駐地點 /// /// /// @@ -164,7 +166,7 @@ public class BpsnDAC : Wellan.Data.BPSN_DAC } /// - /// oHثenaI + /// 取得人員目前派駐地點 /// /// /// @@ -182,7 +184,7 @@ public class BpsnDAC : Wellan.Data.BPSN_DAC } /// - /// oHqll + /// 取得人員電子郵件 /// /// /// @@ -197,7 +199,7 @@ public class BpsnDAC : Wellan.Data.BPSN_DAC } /// - /// oHqll2 + /// 取得人員電子郵件2 /// /// /// @@ -212,10 +214,10 @@ public class BpsnDAC : Wellan.Data.BPSN_DAC } /// - /// oYHݤqW + /// 取得某日期人員所屬公司名稱 /// - /// us - /// + /// 員工編號 + /// 日期 /// public List GetCompanyByDate(string bpeno, DateTime date) { @@ -243,7 +245,7 @@ public class BpsnDAC : Wellan.Data.BPSN_DAC } /// - /// oH¾ + /// 取得人員到職日期 /// /// /// @@ -258,7 +260,7 @@ public class BpsnDAC : Wellan.Data.BPSN_DAC } /// - /// oH¾ + /// 取得人員離職日期 /// /// /// @@ -287,12 +289,238 @@ public class BpsnDAC : Wellan.Data.BPSN_DAC } /// - /// XYb¾u + /// 取出某日期在職的員工 /// - /// + /// 日期 /// public DataTable GetOnBoardEmployee(DateTime date) { return Query.Select(Query.NewCommand("SELECT * FROM BPSNView WHERE dbo.EmployeeOnboard(BPENO, @DATE) = 1")); } + private const string GridSelectColumns = + @" A2.* + , B.RAPYN, C.BPPYM RAPYNX, B.RABRH, D.BSSCR RABRHX, B.RAJNO, E.BPJME RAJNOX, B.RACNO + , F.BPCME RACNOX, ISNULL(H.RPCPY, G.RPCPY) AS RPCPY "; + + // 只 join 該頁明細用 (明細主表為 A2) + private const string GridDetailJoins = + @" LEFT OUTER JOIN RASN B ON A2.BPENO=B.RANUM AND (B.RAEDT IS NULL OR (B.RASDT <= @TODAY AND B.RAEDT >= @TODAY)) + LEFT OUTER JOIN BPAA C ON B.RAPYN=C.BPPYN + LEFT OUTER JOIN BCLS D ON D.BSCLS=B.RABRH AND D.BSPAR='BPSNBRH' + LEFT OUTER JOIN BPJA E ON E.BPPYN=B.RAPYN AND E.BPJNO=B.RAJNO + LEFT OUTER JOIN BPJB F ON F.BPPYN=B.RAPYN AND F.BPJNO=B.RAJNO AND F.BPCNO=B.RACNO + LEFT OUTER JOIN RPIR G ON A2.BPENO=G.RPENO AND G.RPREN=(SELECT MAX(RPREN) FROM RPIR WHERE RPENO=A2.BPENO) + LEFT OUTER JOIN RPIB H ON G.RPREN=H.RPREN AND H.RPITM=(SELECT MAX(RPITM) FROM RPIB WHERE RPREN=H.RPREN) "; + + /// + /// 人員資料 Grid 分頁查詢 + /// + [DataObjectMethod(DataObjectMethodType.Select)] + public DataTable 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) + { + DbCommand cmd = Query.NewCommand(); + + StringBuilder filter = new StringBuilder(); + BuildGridFilter(filter, cmd, BPENO, BPNME, BPENM, BPIDN, BPEDY_TYPE, BPDPT_TYPE, BPSDY1, BPSDY2, BPEDY1, BPEDY2, canSearch, userId); + + // 先在窄集合 (BPSNView) 依 BPENO 排序計算 ROWID,取出該頁的 BPENO 後, + // 才 join 明細表,避免深頁時對整個 join 結果計算 ROW_NUMBER()。 + cmd.CommandText = String.Format( + @"SELECT {0} + FROM + ( + SELECT A.BPENO, ROW_NUMBER() OVER (ORDER BY A.BPENO) AS ROWID + FROM BPSNView A WHERE 1=1 {1} + ) R + JOIN BPSNView A2 ON A2.BPENO = R.BPENO + {2} + WHERE R.ROWID BETWEEN @STARTROWINDEX + 1 AND @STARTROWINDEX + @MAXIMUMROWS + ORDER BY R.ROWID", + GridSelectColumns, filter.ToString(), GridDetailJoins); + cmd.Parameters.Add(Query.NewParameter("STARTROWINDEX", startRowIndex)); + cmd.Parameters.Add(Query.NewParameter("MAXIMUMROWS", maximumRows)); + + return Query.Select(cmd); + } + + /// + /// 人員資料 Grid 查詢筆數 + /// + [DataObjectMethod(DataObjectMethodType.Select)] + public int SelectCount( + 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) + { + DbCommand cmd = Query.NewCommand(); + + StringBuilder filter = new StringBuilder(); + BuildGridFilter(filter, cmd, BPENO, BPNME, BPENM, BPIDN, BPEDY_TYPE, BPDPT_TYPE, BPSDY1, BPSDY2, BPEDY1, BPEDY2, canSearch, userId); + + cmd.CommandText = String.Format("SELECT COUNT(*) FROM BPSNView A WHERE 1=1 {0}", filter.ToString()); + return WDAC.GetInt32Value(Query.ExecuteScalar(cmd)); + } + + /// + /// 取得指定人員編號在查詢結果中的序號 (0 起算) + /// + public int SelectBPENOIndex( + string targetBPENO, + 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) + { + DbCommand cmd = Query.NewCommand(); + + StringBuilder filter = new StringBuilder(); + BuildGridFilter(filter, cmd, BPENO, BPNME, BPENM, BPIDN, BPEDY_TYPE, BPDPT_TYPE, BPSDY1, BPSDY2, BPEDY1, BPEDY2, canSearch, userId); + + cmd.CommandText = String.Format( + @"SELECT ROWID - 1 FROM + ( + SELECT A.BPENO, ROW_NUMBER() OVER (ORDER BY A.BPENO) AS ROWID + FROM BPSNView A WHERE 1=1 {0} + ) AS TMP WHERE BPENO = @TARGET", + filter.ToString()); + cmd.Parameters.Add(Query.NewParameter("TARGET", targetBPENO)); + + object o = Query.ExecuteScalar(cmd); + return (o is Int32 || o is Int64) ? WDAC.GetInt32Value(o) : -1; + } + + /// + /// 組出人員資料 Grid 的查詢條件 (與舊 AddSearch 行為一致) + /// + private void BuildGridFilter( + StringBuilder sb, + DbCommand cmd, + 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) + { + cmd.Parameters.Add(Query.NewParameter("TODAY", DateTime.UtcNow.AddHours(8).Date)); + + if (!canSearch) + { + if (!string.IsNullOrEmpty(userId)) + { + sb.Append(" AND BPENO LIKE @BPENO1 "); + cmd.Parameters.Add(Query.NewParameter("BPENO1", userId + "%")); + } + return; + } + + if (!string.IsNullOrEmpty(BPENO)) + { + sb.Append(" AND BPENO LIKE @BPENO1 "); + cmd.Parameters.Add(Query.NewParameter("BPENO1", BPENO + "%")); + } + if (!string.IsNullOrEmpty(BPNME)) + { + sb.Append(" AND BPNME LIKE @BPNME1 "); + cmd.Parameters.Add(Query.NewParameter("BPNME1", BPNME + "%")); + } + if (!string.IsNullOrEmpty(BPENM)) + { + sb.Append(" AND BPENM LIKE @BPENM1 "); + cmd.Parameters.Add(Query.NewParameter("BPENM1", BPENM + "%")); + } + if (!string.IsNullOrEmpty(BPIDN)) + { + sb.Append(" AND BPIDN LIKE @BPIDN1 "); + cmd.Parameters.Add(Query.NewParameter("BPIDN1", BPIDN + "%")); + } + + switch (BPEDY_TYPE) + { + case "1": + sb.Append(" AND (BPEDY IS NULL OR BPEDY >= @TODAY) "); + break; + case "2": + sb.Append(" AND (BPEDY < @TODAY) "); + break; + } + + switch (BPDPT_TYPE) + { + case "1": + sb.AppendFormat(" AND BPDPT IN {0} ", PublicVariable.InnerDepartment); + break; + case "2": + sb.AppendFormat(" AND BPDPT NOT IN {0} ", PublicVariable.InnerDepartment); + break; + } + + AppendRange(sb, cmd, "BPSDY", BPSDY1, BPSDY2); + AppendRange(sb, cmd, "BPEDY", BPEDY1, BPEDY2); + + if (!IsManager(userId)) + { + sb.Append(" AND BPENO NOT IN ('00001','00002','00003') "); + } + } + + private void AppendRange(StringBuilder sb, DbCommand cmd, string field, string value1, string value2) + { + bool has1 = !string.IsNullOrEmpty(value1); + bool has2 = !string.IsNullOrEmpty(value2); + + if (has1 && has2) + { + sb.AppendFormat(" AND {0} >= @{0}1 AND {0} <= @{0}2 ", field); + cmd.Parameters.Add(Query.NewParameter(field + "1", value1)); + cmd.Parameters.Add(Query.NewParameter(field + "2", value2)); + } + else if (has1) + { + sb.AppendFormat(" AND {0} LIKE @{0}1 ", field); + cmd.Parameters.Add(Query.NewParameter(field + "1", value1 + "%")); + } + else if (has2) + { + sb.AppendFormat(" AND {0} LIKE @{0}2 ", field); + cmd.Parameters.Add(Query.NewParameter(field + "2", value2 + "%")); + } + } } diff --git a/Web/forms/worker/fmBpsn.ascx b/Web/forms/worker/fmBpsn.ascx index 9663456..9c507d4 100644 --- a/Web/forms/worker/fmBpsn.ascx +++ b/Web/forms/worker/fmBpsn.ascx @@ -215,12 +215,25 @@ TextMessageId="" Visible="False"> - + + + + + + + + + + + + + + + + = @TODAY)) - LEFT OUTER JOIN BPAA C ON B.RAPYN=C.BPPYN - LEFT OUTER JOIN BCLS D ON D.BSCLS=B.RABRH AND D.BSPAR='BPSNBRH' - LEFT OUTER JOIN BPJA E ON E.BPPYN=B.RAPYN AND E.BPJNO=B.RAJNO - LEFT OUTER JOIN BPJB F ON F.BPPYN=B.RAPYN AND F.BPJNO=B.RAJNO AND F.BPCNO=B.RACNO - LEFT OUTER JOIN RPIR G ON A.BPENO=G.RPENO AND G.RPREN=(SELECT MAX(RPREN) FROM RPIR WHERE RPENO=A.BPENO) - LEFT OUTER JOIN RPIB H ON G.RPREN=H.RPREN AND H.RPITM=(SELECT MAX(RPITM) FROM RPIB WHERE RPREN=H.RPREN) "; - private string where = " WHERE 1=1 "; - // private string order = " ORDER BY BPENO "; private bool fileOK = false; internal BprmDAC bprmDAC; @@ -163,7 +146,6 @@ public partial class forms_worker_fmBpsn : Wellan.Web.UI.WUserControlBase dsBPSNForm.DeleteCommand = @"DELETE BPSN WHERE BPENO = @OLD_BPENO AND APPNO = @OLD_APPNO"; dsBPSNForm.ConnectionString = ConnectionString; - dsBPSNGrid.ConnectionString = ConnectionString; dsBPSN_BPTYP.ConnectionString = ConnectionString; dsBPSN_BPGNR.ConnectionString = ConnectionString; dsBPSN_BPSGR.ConnectionString = ConnectionString; @@ -298,70 +280,27 @@ public partial class forms_worker_fmBpsn : Wellan.Web.UI.WUserControlBase _BPEDY_TYPE = ddlBPEDY.SelectedValue; _BPDPT_TYPE = ddlBPDPT.SelectedValue; - StringBuilder sql = new StringBuilder(); - sql.Append(select); - sql.Append(from); - sql.Append(where); - dsBPSNGrid.SelectParameters.Clear(); - dsBPSNGrid.SelectParameters.Add("TODAY", String.Format(WGuard1.InvariantCulture, "{0:yyyy-MM-dd}", DateTime.UtcNow.AddHours(8).Date)); - if (canSearch) - { - AddSearch(sql, dsBPSNGrid, "BPENO", _BPENO, "", true); - AddSearch(sql, dsBPSNGrid, "BPNME", _BPNME, "", true); - AddSearch(sql, dsBPSNGrid, "BPENM", _BPENM, "", true); - AddSearch(sql, dsBPSNGrid, "BPIDN", _BPIDN, "", true); - switch (_BPEDY_TYPE) - { - case "1": - sql.Append(" AND (BPEDY IS NULL OR BPEDY >= @TODAY) "); - break; - case "2": - sql.Append(" AND (BPEDY < @TODAY)"); - break; - } - switch (_BPDPT_TYPE) - { - case "1": - sql.Append(String.Format(" AND BPDPT IN {0} ", PublicVariable.InnerDepartment)); - break; - case "2": - sql.Append(String.Format(" AND BPDPT NOT IN {0} ", PublicVariable.InnerDepartment)); - break; - } - AddSearch(sql, dsBPSNGrid, "BPSDY", _BPSDY1, _BPSDY2, true); - AddSearch(sql, dsBPSNGrid, "BPEDY", _BPEDY1, _BPEDY2, true); - - if (!BpsnDAC.IsManager(UserId)) - { - sql.Append(" AND BPENO NOT IN ('00001','00002','00003') "); - } - } - else - { - AddSearch(sql, dsBPSNGrid, "BPENO", UserId, "", true); - } - // sql.Append(order); - dsBPSNGrid.SelectCommand = sql.ToString(); + dsBPSNGrid.SelectParameters["BPENO"].DefaultValue = _BPENO; + dsBPSNGrid.SelectParameters["BPNME"].DefaultValue = _BPNME; + dsBPSNGrid.SelectParameters["BPENM"].DefaultValue = _BPENM; + dsBPSNGrid.SelectParameters["BPIDN"].DefaultValue = _BPIDN; + dsBPSNGrid.SelectParameters["BPEDY_TYPE"].DefaultValue = _BPEDY_TYPE; + dsBPSNGrid.SelectParameters["BPDPT_TYPE"].DefaultValue = _BPDPT_TYPE; + dsBPSNGrid.SelectParameters["BPSDY1"].DefaultValue = _BPSDY1; + dsBPSNGrid.SelectParameters["BPSDY2"].DefaultValue = _BPSDY2; + dsBPSNGrid.SelectParameters["BPEDY1"].DefaultValue = _BPEDY1; + dsBPSNGrid.SelectParameters["BPEDY2"].DefaultValue = _BPEDY2; + dsBPSNGrid.SelectParameters["canSearch"].DefaultValue = canSearch.ToString(); + dsBPSNGrid.SelectParameters["userId"].DefaultValue = UserId; if (!string.IsNullOrEmpty(GetStringParam("BPENO")) || !string.IsNullOrEmpty(WDAC.GetStringValue(ViewState["RPENO"]))) { string bpeno = ""; if (!string.IsNullOrEmpty(GetStringParam("BPENO"))) bpeno = GetStringParam("BPENO"); if (!string.IsNullOrEmpty(WDAC.GetStringValue(ViewState["RPENO"]))) bpeno = WDAC.GetStringValue(ViewState["RPENO"]); - var cmdIndex = Query.NewCommand(); - cmdIndex.CommandText = string.Format("SELECT ROWID - 1 FROM ( {0} ) T WHERE BPENO = @TARGET", dsBPSNGrid.SelectCommand); - Query.AddParam(cmdIndex, "TODAY", DateTime.UtcNow.AddHours(8).Date); - Query.AddParam(cmdIndex, "BPENO1", _BPENO); - Query.AddParam(cmdIndex, "BPNME1", _BPNME); - Query.AddParam(cmdIndex, "BPENM1", _BPENM); - Query.AddParam(cmdIndex, "BPIDN1", _BPIDN); - Query.AddParam(cmdIndex, "BPSDY1", _BPSDY1); - Query.AddParam(cmdIndex, "BPSDY2", _BPSDY2); - Query.AddParam(cmdIndex, "BPEDY1", _BPEDY1); - Query.AddParam(cmdIndex, "BPEDY2", _BPEDY2); - Query.AddParam(cmdIndex, "TARGET", bpeno); - var o = Query.ExecuteScalar(cmdIndex); - int idx = (o is Int32 || o is Int64) ? WDAC.GetInt32Value(o) : -1; + + int idx = new BpsnDAC(Query).SelectBPENOIndex( + bpeno, _BPENO, _BPNME, _BPENM, _BPIDN, _BPEDY_TYPE, _BPDPT_TYPE, _BPSDY1, _BPSDY2, _BPEDY1, _BPEDY2, canSearch, UserId); if (idx >= 0) { @@ -455,9 +394,9 @@ public partial class forms_worker_fmBpsn : Wellan.Web.UI.WUserControlBase GridView1.DataBind(); } - protected void dsBPSNGrid_Selected(object sender, SqlDataSourceStatusEventArgs e) + protected void dsBPSNGrid_Selected(object sender, ObjectDataSourceStatusEventArgs e) { - if (e.AffectedRows == 0) + if (e.ReturnValue is Int32 && WDAC.GetInt32Value(e.ReturnValue) == 0) { if (MultiView1.ActiveViewIndex == 0) {