feat: fmBPSN GridView 改用 ObjectDataSource 並採 KEYSET 分頁
- 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(盤點、計畫、效能比較)
This commit is contained in:
@@ -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 編譯通過 |
|
||||
+243
-15
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 員工資料的資料處理類別
|
||||
/// 員工資料的資料處理類別
|
||||
/// </summary>
|
||||
[DataObject(true)]
|
||||
public class BpsnDAC : Wellan.Data.BPSN_DAC
|
||||
{
|
||||
private DbCommand cmdSelect;
|
||||
@@ -115,7 +117,7 @@ public class BpsnDAC : Wellan.Data.BPSN_DAC
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取得人員目前派駐專案
|
||||
/// 取得人員目前派駐專案
|
||||
/// </summary>
|
||||
/// <param name="bpeno"></param>
|
||||
/// <returns></returns>
|
||||
@@ -125,7 +127,7 @@ public class BpsnDAC : Wellan.Data.BPSN_DAC
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取得人員目前派駐專案
|
||||
/// 取得人員目前派駐專案
|
||||
/// </summary>
|
||||
/// <param name="bpeno"></param>
|
||||
/// <returns></returns>
|
||||
@@ -154,7 +156,7 @@ public class BpsnDAC : Wellan.Data.BPSN_DAC
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取得人員目前派駐地點
|
||||
/// 取得人員目前派駐地點
|
||||
/// </summary>
|
||||
/// <param name="bpeno"></param>
|
||||
/// <returns></returns>
|
||||
@@ -164,7 +166,7 @@ public class BpsnDAC : Wellan.Data.BPSN_DAC
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取得人員目前派駐地點
|
||||
/// 取得人員目前派駐地點
|
||||
/// </summary>
|
||||
/// <param name="bpeno"></param>
|
||||
/// <returns></returns>
|
||||
@@ -182,7 +184,7 @@ public class BpsnDAC : Wellan.Data.BPSN_DAC
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取得人員電子郵件
|
||||
/// 取得人員電子郵件
|
||||
/// </summary>
|
||||
/// <param name="bpeno"></param>
|
||||
/// <returns></returns>
|
||||
@@ -197,7 +199,7 @@ public class BpsnDAC : Wellan.Data.BPSN_DAC
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取得人員電子郵件2
|
||||
/// 取得人員電子郵件2
|
||||
/// </summary>
|
||||
/// <param name="bpeno"></param>
|
||||
/// <returns></returns>
|
||||
@@ -212,10 +214,10 @@ public class BpsnDAC : Wellan.Data.BPSN_DAC
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取得某日期人員所屬公司名稱
|
||||
/// 取得某日期人員所屬公司名稱
|
||||
/// </summary>
|
||||
/// <param name="bpeno">員工編號</param>
|
||||
/// <param name="date">日期</param>
|
||||
/// <param name="bpeno">員工編號</param>
|
||||
/// <param name="date">日期</param>
|
||||
/// <returns></returns>
|
||||
public List<string> GetCompanyByDate(string bpeno, DateTime date)
|
||||
{
|
||||
@@ -243,7 +245,7 @@ public class BpsnDAC : Wellan.Data.BPSN_DAC
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取得人員到職日期
|
||||
/// 取得人員到職日期
|
||||
/// </summary>
|
||||
/// <param name="bpeno"></param>
|
||||
/// <returns></returns>
|
||||
@@ -258,7 +260,7 @@ public class BpsnDAC : Wellan.Data.BPSN_DAC
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取得人員離職日期
|
||||
/// 取得人員離職日期
|
||||
/// </summary>
|
||||
/// <param name="bpeno"></param>
|
||||
/// <returns></returns>
|
||||
@@ -287,12 +289,238 @@ public class BpsnDAC : Wellan.Data.BPSN_DAC
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取出某日期在職的員工
|
||||
/// 取出某日期在職的員工
|
||||
/// </summary>
|
||||
/// <param name="date">日期</param>
|
||||
/// <param name="date">日期</param>
|
||||
/// <returns></returns>
|
||||
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) ";
|
||||
|
||||
/// <summary>
|
||||
/// 人員資料 Grid 分頁查詢
|
||||
/// </summary>
|
||||
[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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 人員資料 Grid 查詢筆數
|
||||
/// </summary>
|
||||
[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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取得指定人員編號在查詢結果中的序號 (0 起算)
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 組出人員資料 Grid 的查詢條件 (與舊 AddSearch 行為一致)
|
||||
/// </summary>
|
||||
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 + "%"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,12 +215,25 @@
|
||||
TextMessageId="" Visible="False"></cc1:WLabel>
|
||||
</asp:View>
|
||||
<asp:View ID="EditView" runat="server">
|
||||
<asp:SqlDataSource ID="dsBPSNGrid"
|
||||
runat="server" ConflictDetection="CompareAllValues"
|
||||
SelectCommand="SELECT * FROM BPSNView WHERE 1=0"
|
||||
ConnectionString="Data Source=(local);Initial Catalog=THINKYU_WEB;Persist Security Info=True;User ID=sa"
|
||||
ProviderName="System.Data.SqlClient"
|
||||
OnSelected="dsBPSNGrid_Selected"></asp:SqlDataSource>
|
||||
<asp:ObjectDataSource ID="dsBPSNGrid"
|
||||
runat="server" TypeName="BpsnDAC"
|
||||
SelectMethod="SelectPage" SelectCountMethod="SelectCount"
|
||||
EnablePaging="true" OnSelected="dsBPSNGrid_Selected">
|
||||
<SelectParameters>
|
||||
<asp:Parameter Name="BPENO" Type="String" />
|
||||
<asp:Parameter Name="BPNME" Type="String" />
|
||||
<asp:Parameter Name="BPENM" Type="String" />
|
||||
<asp:Parameter Name="BPIDN" Type="String" />
|
||||
<asp:Parameter Name="BPEDY_TYPE" Type="String" />
|
||||
<asp:Parameter Name="BPDPT_TYPE" Type="String" />
|
||||
<asp:Parameter Name="BPSDY1" Type="String" />
|
||||
<asp:Parameter Name="BPSDY2" Type="String" />
|
||||
<asp:Parameter Name="BPEDY1" Type="String" />
|
||||
<asp:Parameter Name="BPEDY2" Type="String" />
|
||||
<asp:Parameter Name="canSearch" Type="Boolean" />
|
||||
<asp:Parameter Name="userId" Type="String" />
|
||||
</SelectParameters>
|
||||
</asp:ObjectDataSource>
|
||||
<asp:SqlDataSource ID="dsBPSNForm"
|
||||
runat="server"
|
||||
ConflictDetection="CompareAllValues"
|
||||
|
||||
@@ -24,23 +24,6 @@ public partial class forms_worker_fmBpsn : Wellan.Web.UI.WUserControlBase
|
||||
private string _BPEDY1 = "";
|
||||
private string _BPEDY2 = "";
|
||||
|
||||
private string select =
|
||||
@" SELECT
|
||||
A.*
|
||||
, 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
|
||||
, ROW_NUMBER() OVER (ORDER BY A.BPENO) ROWID ";
|
||||
private string from =
|
||||
@" FROM BPSNView A
|
||||
LEFT OUTER JOIN RASN B ON A.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 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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user