使用VS2005中的ObjectDataSource實作技巧

ObjectDataSource的用途

ObjectDataSource是VS2005新增的資料來源元件,在VS2005中,List類型之UI元件都支援DataSource定義資料來源的能力,而DataSource可以是下列的任何一項:
  1. SqlDataSource - 直接連接資料庫
  2. XMLDataSource - 連結XML檔案
  3. AccessDataSource - 連結Access檔案
  4. SiteMap格式的SiteMapDataSource
  5. ObjectDataSource - 自行撰寫Class寫出資料來源的界接,讓VS2005的UI元件聯繫個功能的Method來做資料的取得。

每一個具備List功能的UI元件都可以去聯繫DataSource,而其中ObjectDataSource是可以做客製化設計的一個資料來源。

要提供給ObjectDataSource的Class必須提供Static的Method,可以分別針對特定功能(Select/Insert/Delete/Update)設計該Static Method,每個特定功能的Method都規範的傳遞值與回傳值。
  • Select - Select Method允許自行定義0個以上任意型態的傳入參數,輸入參數可以對應至其他UI元件所選擇的數值、QueryString參數、Session儲存值、Cookie值或自行輸入數值來決定Select範圍。對於回傳值部分則限制必須為下列其中一項:DataTable、DataView、DataSet、具備IEnumerable型態的Object。
  • Insert/Delete/Update - 此類Method是提供給GridView、DetailView或FormView等同時具備新增/編輯/刪除功能的UI元件,這些Method都不需要有回傳值,而傳入參數則是由該UI元件呼叫填入而必須存在,參數建議是採用較易管理的商業邏輯,透過一個獨立的class設計,而該class具備各種資料名稱的Property。當Method被呼叫的同時,DataBind對應的個資料欄位,就會填入各相同名稱的Property內,以方便後續的處理。
我用一個簡單的例子來說明ObjectDataSource的便利性,首先我先定義一個Class來存放一個DataBase Table內的基本資料形態,這是一個學校單位表:

/// 單位資料
class Department{
private string pShortName, pFullName, pDeptCode;
public Department() {
};
/// 單位縮寫
public string ShortName {
get {
return this.pShortName;
}
set {
this.pShortName = value;
}
}
/// 單位全銜
public string FullName {
get {
return this.pFullName;
}
set {
this.pFullName = value;
}
}
/// 單位代碼
public string DeptCode {
get {
return this.pDeptCode;
}
set {
this.pDeptCode = value;
}
}
};

在擁有一個可以存放單位資料的Class之後,我在另外設計一個Collection負責來存放n筆Department Class,由於此類型的Collection我想針對Department,但是又不想分別為每個Unit Class重新設計,所以我把它設計成泛型的Class並且繼承自CollectionBase元件,因為CollectionBase具備IEnumerable介面,因此符合Select Method回傳值定義。
/// 抽象化資料集合元件
public class DataCollection : CollectionBase {
/// 新增物件
public int Add(T pT) {
return this.List.Add(pT);
}
/// 新增物件
public void Insert(int pIndex, T pT) {
this.List.Insert(pIndex, pT);
}
/// 刪除物件
public void Remove(T pT) {
this.List.Remove(pT);
}
/// 檢查物件
public bool Contains(T pT) {
return this.List.Contains(pT);
}
/// 取得物件位置
public int IndexOf(T pT) {
return this.List.IndexOf(pT);
}
/// 取得索引之物件
public T this[int pIndex] {
get {
return (T)this.List[pIndex];
}
set {
this.List[pIndex] = value;
}
}
}
透過上面泛型DataCollection Class的設計,如果我今天需要一個Department集合時,我只要宣告DataCollection aCollection;就可以取得一個能夠存放Deparment的集合元件,而想要存放其他型態的時候,只要變更形態就好不需要重新撰寫一個新的Collection非常實用。
再來我們就要針對資料的取得以及如果將窗口建立好讓UI原件可以透過ObjectDataSource取得資料來做努力了,首先我們先針對資料的取得分別設計Insert/Delete/Update/Select等功能的Class(範例將只有針對Select實作),在此我們稱呼他為DepartmentProvider。
/// Department資料實現
public class DepartmentProvider {
private Oracle.DataAccess.Client.OracleConnection pConnection = null;
private string ConnectionString="請自己填上ConnecitonString";
/// 建立預設資料庫連結元件
public DepartmentProvider() {
pConnection = new Oracle.DataAccess.Client.OracleConnection(ConnectionString);
}
/// 取得一級單位(限定學院)
public DataCollection SelectFirstClass() {
string pSql = @"select DEPT_CODE,DEPT_CNAME,DEPT_CNAME_A from DEPARTMENT where dept_code like 'B_X'";
DataCollection pDepartmentCollection = new DataCollection();
try {
this.pConnection.Open();
Oracle.DataAccess.Client.OracleDataAdapter pAdapter = new Oracle.DataAccess.Client.OracleDataAdapter("", pConnection);
pAdapter.SelectCommand.CommandText = pSql;
pAdapter.SelectCommand.Parameters.Clear();
DataSet pDataSet = new DataSet();
pAdapter.Fill(pDataSet);
pConnection.Close();
foreach (DataRow pRow in pDataSet.Tables[0].Rows) {
string pDeptCode = Convert.ToString(pRow["DEPT_CODE"]).Trim();
string pFullName = Convert.ToString(pRow["DEPT_CNAME"]).Trim();
string pShortName = Convert.ToString(pRow["DEPT_CNAME_A"]).Trim();
Department pDepartment = new Department(pShortName, pFullName, pDeptCode);
pDepartmentCollection.Add(pDepartment);
}
}
finally {
if (pConnection.State != System.Data.ConnectionState.Closed) {
pConnection.Close();
}
}
return pDepartmentCollection;
}
/// 取得二級單位(限定系或所)
public DataCollection SelectSecondClass(string pClass) {
if (pClass == "")
return null;
string pSql = @"select DEPT_CODE,DEPT_CNAME,DEPT_CNAME_A from DEPARTMENT where MASTER_DEPTCODE=:MASTER_DEPTCODE AND SUBSTR(DEPT_CODE,1,1) in ('U','G') ORDER BY DEPT_CODE DESC";
if (pClass == "ALL") {
pSql = @"select DEPT_CODE,DEPT_CNAME,DEPT_CNAME_A from DEPARTMENT where SUBSTR(DEPT_CODE,1,1) in ('U','G') ORDER BY DEPT_CODE DESC";
}
DataCollection pDepartmentCollection = new DataCollection();
try {
pConnection.Open();
Oracle.DataAccess.Client.OracleDataAdapter pAdapter = Oracle.DataAccess.Client.OracleDataAdapter("", pConnection);
pAdapter.SelectCommand.CommandText = pSql;
pAdapter.SelectCommand.Parameters.Clear();
pAdapter.SelectCommand.Parameters.Add(new Oracle.DataAccess.Client.OracleParameter("MASTER_DEPTCODE", pClass));
DataSet pDataSet = new DataSet();
pAdapter.Fill(pDataSet);
pConnection.Close();
foreach (DataRow pRow in pDataSet.Tables[0].Rows) {
string pDeptCode = Convert.ToString(pRow["DEPT_CODE"]).Trim();
string pFullName = Convert.ToString(pRow["DEPT_CNAME"]).Trim();
string pShortName = Convert.ToString(pRow["DEPT_CNAME_A"]).Trim();
Department pDepartment = new Department(pShortName, pFullName, pDeptCode);
pDepartmentCollection.Add(pDepartment);
}
}
finally {
if (pConnection.State != System.Data.ConnectionState.Closed) {
pConnection.Close();
}
}
return pDepartmentCollection;
}
}
在具備資料基本儲存元件Department、資料集合元件DataCollection、資料的提供者DepartmentProvider之後,剩下最後的一個元件必須提供一個窗口給UI元件來Select或未來另外在實作Insert/Delete/Update功能的時候使用,在這個負責提供相關功能的元件,在此把它取名為DepartmentServices。
[System.ComponentModel.DataObject]
public class DepartmentServices {
private static DepartmentProvider _DepartmentProvider = null;
/// 取得資料庫連結元件
public static DepartmentProvider LoadProvider() {
if (_DepartmentProvider == null) {
_DepartmentProvider = new DepartmentProvider();
}
return _DepartmentProvider;
}
/// 取得一級單位
[System.ComponentModel.DataObjectMethod(System.ComponentModel.DataObjectMethodType.Select)]
public static DataCollection SelectFirstClass() {
return LoadProvider().SelectFirstClass();
}
/// 取得二級單位
/// pClass:所屬一級單位
[System.ComponentModel.DataObjectMethod(System.ComponentModel.DataObjectMethodType.Select)]
public static DataCollection SelectSecondClass(string pClass) {
return LoadProvider().SelectSecondClass(pClass);
}
}
在DepartmentServices中,透過AttributeUsageAttribute修飾的方式,可以在ObjectDataSource在選擇對應Method的時候提供分類已經篩選功能,在上述的Class中,我們將Method都定義為Select類別,以方便快速取得。在整個系統先做一次Complie確認無誤之後,我們將在UI界面上開始使用此服務所帶來的好處(寫這麼多了沒好處幹嘛寫 囧)。在具備List功能UI元件上(ComoboBox),我們將直接使用ObjectDataSource,並且挑選使用DepartmentServices內的SelectFirstClass來當該ObjectDataSource的SelectMethod。首先當然是設定該UI元件聯繫至新的ObjectDataSource。

下一步則是設定該ObjectDataSource所對應的Class,在此我們將對應至DepartmentServices。

設定ObjectDataSource的SelectMethod為SelectFirstClass,讓此UI元件設計顯示一級單位的資料。

設定此UI元件Display/Value分別為Department Class的哪一個Property。

先看看到目前為止的結果,符合預期狀況,DropDownList內在測試下直接存放一級單位資料於Items內提供挑選。

透過GridView可以列出多筆資料的優點,我在開一個新的ObjectDataSource而該SelectMethod這次選擇SelectSecondClass,由於該Method需要傳遞一個pClass來設定一級單位,因此將pClass來源設定為Control並且指向剛剛成功可以選擇一級單位的DropDownList,並記得將DropDownList的AutoPostBack設為True以方便立即反應。

我們再次Demo原本的網頁看是否符合預期。

以ObjectDataSource來做資料面的管控雖然需要多寫幾個Class,但是我們可以將Data/View兩者之間很乾淨的切開來,當然View部分可能還是需要部分的程式碼控制來增強互動,但是ObjectDataSource的方式不僅僅是讓資料面回歸到單純的資料提供者的角色,也同時避免UI經常性用程式碼在填入資料。而ObjectDataSource在此例子中是透過讀取DataBase的方式來做使用,雖然已經具備SqlDataSource元件可以提供讀取資料庫了,那還有需要這麼做嗎?我個人覺得是必要的。當然簡單的系統是可以直接透過SqlDataSource去存取資料庫即可,不需要如此複雜,而採用ObjectDataSource是為了方便在現在資料在作加工的動作,畢竟Class的Property是自行定義的,並不是絕對必須要跟資料庫的Table保持一致性,可以自行增加一些暫存型資訊(如從Session/Cookie取得),或降低複雜度減少Property的數量(如50個field的Table只用到10個field),當然也能多個Table資料合併成一個Class來使用,或部分Property的值在被提取的時候才實際作DataBase的Select,最重要的是由於資料來源是自行設計的,因此資料不必是DataBase、XML、PlainText、WebService、HTML,只要有辦法產生對應的資料即可,所以在暫時需要TestPartten的時候,也可以在Provider上作手腳。方法可以在Provider內自行決定與採用,畢竟這是一個架構並沒有要求實作方式。

沒有留言:

橫式廣告