VFP CursorAdapter 起步一(作者:Doug Hennig 譯者:fbilo)

CursorAdapter 類是 VFP 8 中最重要的新功能之一,因爲它提供了一種簡單易用、接口統一的訪問遠程數據源方式。在這個月的文章裏,Dung Hennig 將向你展示 CursorAdapter 及它的工作方式。下個月,我們將再學習一些高級的用法。
正文:
越來越多的 VFP 程序員開始把他們的數據儲存到象 SQL Server 或者 Oracle 這樣的 VFP 表以外的數據倉庫中去了。有許多原因導致了這種情況,包括 VFP 表的脆弱性(不管是想象中的還是確實如此)、安全性、數據庫的容量、以及通用性的標準等等。MicroSoft 已經在每一個版本中都使得訪問非VFP數據更加的簡單,爲了鼓勵這種風氣,它甚至在 VFP 7 光盤中自帶了 MSDE(Microsoft Data Engine,SQL Server 的一個免費、簡裝版)。
不過,訪問一個後臺數據庫從來就比使用 VFP 表要麻煩一些,而你可以使用的機制則多得嚇人:
×× 遠程視圖,它基於 ODBC 連接;
×× SQL Passthrough (SPT) 函數,例如 SQLCONNECT()、SQLEXEC() 和 SQLDISCONNECT(),它們也基於 ODBC 連接;
×× ActiveX Data Objects ,簡稱 ADO,它提供了一個對各種數據庫引擎的 OLE Provider 的一個面對對象訪問方式;
×× XML,它是一個輕量級的、平臺無關的數據傳輸機制。
如果你曾經用這些機制上工作過,有一件事情你可能已經注意到了:它們中的每一種都各不相同。這樣的話,你就必須一個個的學過來,還要把一個已有的應用程序從一種機制轉換到另一種機制,這可不是一件簡單的工作。
由於有了一個新的基礎類 CursorAdapter,在 VFP 8 中訪問遠程數據要比過去的版本中簡單的多。以我之見,CursorAdapter 是 VFP 8 最重要的新功能之一。我認爲它最棒的地方是:
×× 使用 ODBC、ADO、XML 變得非常容易,即時你不熟悉這些技術。
×× 不管你選擇了哪種遠程數據源機制,它都提供一種統一的訪問接口。
×× 從一種機制轉換到另一種機制變得非常的輕鬆。
這裏是上面的最後一個觀點的例子。假設你有一個使用 CursorAdapter 通過 ODBC 來訪問 SQL Server 數據的應用程序,由於某些原因你想要改成使用 ADO 了。對於這種情況,你只需要改動 CursorAdapter 的 DataSourceType 屬性、並改變對後臺數據庫的連接,就全部完成了。你的應用程序中的其它部分不需要知道也不需要關心這些事情;它們看到的只是同一個 Cursor 而不管使用了哪一種機制。
屬性
我們先從查看 CursorAdapter 的屬性、事件和方法開始來學習它。這裏不會討論所有的屬性,只談一下最重要的那些。
DataSourceType
**************
這個屬性是最重要的:它決定了這個類的表現,以及要在其它一些屬性中要怎麼設置。可用的選項有“Native”——意思是使用 VFP 表——或者是 "ODBC"、"ADO" 或 "XML" ,表示你要選用的訪問遠程數據源的方式。
DataSource
***********
這是訪問數據的手段。當 DataSourceType 被設置成“Native”或者“XML”的時候,VFP會忽略這個屬性的設置。對於ODBC,請把這個屬性設置爲一個有效的 ODBC 連接句柄(這意味着你要自己管理連接了)。在ADO的情況下,DataSource 必須是一個 ADO RecordSet,而且它的 ActiveConnection 對象必須被設置爲一個打開的 ADO Connection 對象(你又要自己管理這些了)。
UseDEDataSource
****************
如果這個屬性被設置成了 .T.(默認是 .F.),你可以不管它的 DataSourceType 和 DataSource 屬性,因爲 CursorAdapter 將使用 DataEnvironment 的屬性來代替( VFP 8 給 DataEnvironment 也增加了 DataSourceType 和 DataSource 屬性)。舉例來說,當你想讓在一個數據環境中的所有 CursorAdapters 都使用同一個 ODBC 連接的時候,就可以把它設置爲 .T.。
SelectCmd
**********
除了 XML 的情況以外,這是一個用來取得數據的 SQL Select 命令。在 XML 的情況下,它可以或者是一個能夠被轉換爲一個 Cursor 的有效 XML 字符串(使用內部的 XMLTOCURSOR() 調用),或者是一個能夠返回一個有效的 XML 字符串的表達式。
CursorSchema
************
這個屬性裏保存的是 Cursor 的數據結構,格式就像你在用 CREATE CURSOR 命令的時候用的那樣。這是一個例子:CUST_ID C(6), COMPANY C(30), CONTACT C(30), CITY C(25)。儘管不設置這個屬性而讓 CursorAdapter 在自己建立 Cursor 去決定這個結構也是可以的,不過如果你自己輸入的話,它會工作的更好。如果 CursorSchema 是空的或者不正確,那麼當你打開一個表單的數據環境的時候,就會要麼彈出一個錯誤,要麼就不能通過從 CursorAdapter 中拖放字段到表單上來建立控件。幸運的是,VFP 自帶的 CursorAdapter 生成器可以爲你填充這個屬性。
AllowDelete、AllowInsert、AllowUpdate 和 SendUpdates
****************************************************
這些屬性的默認值是 .T.,它們決定了是否可以刪除、插入和更新和改動是否要被髮送到數據源。
KeyFieldList、 Tables、 UpdatableFieldList、和 UpdateNameList
*************************************************************
這些屬性的用途跟 CURSORSETPROP() 中用到的那些參數的用途是一樣的,如果你想讓 VFP 自動將對 Cursor 的改動提交到數據源,這些屬性就是必須的。
×× KeyFieldList 是一個用逗號分隔的字段列表(不帶別名),這些字段組成 Cursor 的主關鍵字。Tables 是一個用逗號分隔的表名列表。
×× UpdatableFieldList 是一個用逗號分隔的可以被更新的字段名列表(不帶別名)。
×× UpdateNameList 是一個用逗號分隔的列表,它用來讓 Cursor 中的字段名與在表中的字段名相匹配。UpdateNameList 的格式就像 這樣:CURSORFIELDNAME1 TABLE.FIELDNAME1、CURSORFIELDNAME2 TABLE.FIELDNAME2 等等。注意:如果 UpdatableFieldList 不包含表的主鍵字段的名稱(比如說你不想讓用戶可以更新這個字段),在 UpdateNameList 還是必須要有這個字段,否則就不能更新。
Cmd、*CmdDataSource 和 *CmdDataSourceType
*****************************************

如果你想指定讓 VFP 怎樣去刪除、插入和更新數據源中的記錄,你可以給這些屬性設置相應的值——注意,* 的位置是 Delete、Insert 或者 Update。
CursorFill(UseCursorSchema, NoData, Options, Source)
****************************************************
這個方法建立 Cursor,並用來自數據源的數據填充這個 Cursor(你也可以給 NoData 參數傳遞一個 .T.以建立一個空的 Cursor),給第一個參數傳遞 .T. 來使用定義在 CursorSchema 中的遊標數據結構,或者傳遞 .F. 來根據數據源中的結構建立一個相應的結構。MULTILOCKS 必須被設置成 ON,否則這個方法將執行失敗。如果 CursorFill 由於某些原因執行失敗,它不會發生一個錯誤而是返回 .F.,不過你還是可以用 AERROR() 來檢查發生了什麼錯誤(準備苦苦挖掘吧!通常你得到的錯誤信息都不足以告訴你究竟問題在哪裏)。
CursorRefresh()
***************
這個方法類似於 Requery() 函數:它刷新 Cursor 的內容。
Before*() 和 After*()
*********************
CursorAdapter 的幾乎每個方法和事件都有一套 before 和 After 開頭的“hook”事件(hook這個詞中文沒有很好的對應,勉強把它翻譯成“掛鉤”還不如不翻),這樣你就可以自定義 CursorAdapter 的行爲特性了。例如,你可以在 AfterCursorFill 中爲 Cursor 建立索引。在 Before 系列事件中你可以返回一個 .F. 來防止觸發被 hook 的事件發生(類似於數據庫事件)。
示例
*****
這裏是一個示例來自附帶的示例文件 (CursorAdapterExample.prg),它用於從 SQL Server 自帶的 Northwind 數據庫的 Customers 表中取得巴西客戶的某幾個字段數據。產生的 Cursor 是可更新的,所以如果你對 Cursor 中的數據做了某些改動,然後再次運行程序,你會看到剛纔所作的改動已經被保存在後臺數據庫中了。
local lcConnString, ;
loCursor as CursorAdapter, ;
laErrors[1] lcConnString = 'driver=SQL Server;server=(local);' + ;
      'database=Northwind;uid=sa;pwd=;trusted_connection=no'
* 把這裏的密碼改成你自己的數據庫中密碼
loCursor = createobject('CursorAdapter')
with loCursor
  .Alias = 'Customers'
  .DataSourceType = 'ODBC'
  .DataSource = sqlstringconnect(lcConnString)
  .SelectCmd = "select CUSTOMERID, " + ;
      "COMPANYNAME, CONTACTNAME from CUSTOMERS " + ;
      "where COUNTRY = 'Brazil'"
  .KeyFieldList = 'CUSTOMERID'
  .Tables = 'CUSTOMERS'
  .UpdatableFieldList = 'CUSTOMERID, COMPANYNAME, ' + ;
      'CONTACTNAME'
  .UpdateNameList = ;
      'CUSTOMERID CUSTOMERS.CUSTOMERID, ' + ;
      'COMPANYNAME CUSTOMERS.COMPANYNAME, ' + ;
      'CONTACTNAME CUSTOMERS.CONTACTNAME'
  if .CursorFill()
     browse
  else
     aerror(laErrors)
     messagebox(laErrors[2])
  endif
  .CursorFill()
endwith
數據環境和表單的增強
********************
爲了支持新的 CursorAdapter 類,對錶單和數據環境類也做了一些增強。
首先,象我前面提到過的那樣,DataEnvironment 類現在有了 DataSource 和 DataSourceType 屬性。不過它自己並不使用這些屬性,而是給那些在這個數據環境中的那些 UseDEDataSource 被設置成了 .T. 的 CursorAdapter 使用的。其次,現在你可以使用類設計器來可視化的建立 DataEnvironment 的子類了(哇!)。
而對於表單,你可以通過設置新的 DEClass 和 DEClassLibrary 屬性來指定使用一個 DataEnvironment 的子類了。不過這麼做一定要趁早,因爲在這麼幹了以後,原來的數據環境中所有已經做好的東西(Cursor、代碼等等)都會丟失,還好系統會先警告你。表單的一個很酷的新功能是它的 BindControls 屬性——把這個屬性設置爲.F.就可以讓表單在 INIT 的時候不對控件進行數據綁定,而只有當 BindControls 被設置爲 .T. 的時候纔會這樣。這個功能好在哪裏呢?你曾經多少次詛咒過這樣的情況:參數必須被傳遞給表單的INIT事件,而INIT事件卻要等到所有的控件已經初始化並已經綁定到它們的數據源了以後纔會被觸發?要是你想向該表單傳遞一個參數來告訴表單打開哪個表或者其它會影響 ControlSources 的事情的時候該怎麼辦?這個新的屬性讓這些事情變得象打個瞌睡那麼容易。
優點
*****
有大量的理由支持我們使用 CursorAdapters 來代替遠程視圖、SPT、ADO 或者 XML:
×× 每一種機制都有一種不同的接口。對於遠程視圖,你用 USE 命令來打開它們。對於SPT,你要使用 SQLCONNECT() 和 SQLEXEC() 來建立一個 Cursor。對於 ADO,你必須先建立 ADO 的 Connection 對象和 Recordset 對象(根據你取得數據方式的不同,也許還可能需要一個 Command 對象)的實例,並調用它們的 Open 方法。對於 XML,你首先必須從什麼地方獲得一個 XML 字符串(例如在 N 層應用中通過一個 COM 部件,或者在 SQL Server 中通過 SQLXML),然後使用 XMLTOCURSOR() 來把這個字符串轉換成一個 VFP 的 Cursor。編寫對後臺數據源進行更新的代碼也各不相同。所以,當你從一種機制轉換到另一種機制的時候,就必須要去學一種新的技術,還有你可能需要重寫的大量已有的代碼。
不管你使用哪種機制來取得數據,CursorAdapters 都使用同一個統一的接口。通過設置該對象的一些屬性,然後調用它的 CursorFill 方法來取得數據,對這個 Cursor 操作時就像在操作一個普通的 VFP Cursor 一樣,然後調用 TABLEUPDATE() (或者讓VFP自動去處理它)來將更新提交到後臺數據源。
×× 在實際開發的時候,你經常會需要從命令窗口中打開一個 Cursor 來瀏覽它的內容。對於遠程視圖,這麼做是很簡單的,但是對於 SPT、ADO 和 XML,就可能要花上很多力氣了。
而打開由一個 CursorAdapter 的子類產生的 Cursor 幾乎就像打開一個遠程視圖那麼容易:你只需要建立這個子類的實例,然後調用它的 CursorFill 方法就行了。你甚至可以直接在它的 INIT 方法中進行 CursorFill,這樣只要一步就可以完成操作了。
×× 對於遠程視圖來說,升遷一個已有的應用程序會相對容易一些:你只要把數據環境中本地表或視圖的東西換成同名的遠程視圖就行了。但是對於SPT、ADO 或 XML,你可能就必須要全部重寫整個數據訪問方案了。
而用 CursorAdapters 來升遷一個應用程序就會象用 遠程視圖來升遷一樣輕鬆——只要簡單的把數據環境中的 Cursor 對象替換成 CursorAdapters 對象就行了。
×× 在表單和報表設計器中使用遠程視圖來工作是很容易的。你可以給數據環境添加一個遠程視圖,然後就能利用到數據環境提供的可視化設計的優勢了:拖放字段來自動建立控件、通過在屬性窗口中的組合框中選擇來輕鬆的將控件綁定到一個字段等等。SPT、ADO、XML 就不支持可視化設計方式了。
CursorAdapters 與遠程視圖一樣能夠享受到在數據環境中可視化設計的那些優點。
×× 用視圖設計器可以很容易的建立遠程視圖。儘管過去它有着許多限制,尤其是在處理有兩個以上的表相互連接的視圖的時候,可現在,VFP 8 已經修正這些問題中的大多數,並且添加了許多新功能,例如雙向編輯:你可以在 SQL 窗口中修改代碼,然後就能看到這些改動被反映到視圖設計器的可視化部分中了。而對於 SPT、ADO 和 XML,要做的工作就多的多,因爲每樣東西你都必須自己寫代碼:建立和關閉連接、要執行的 SQL Select 語句等等。
VFP 8 包含了一個 CursorAdapters 生成器,用了它,可以只需要很少的工作就可以設置好那些對於取得和更新數據來說相當重要的屬性。它甚至還包含了一個 SelectCmd 生成器,這個生成器的可視化程度就像是視圖設計器一樣,它讓你可以通過使用一個“mover”控件來選擇應該從遠程表中取得那些字段。
×× 將遠程視圖和ADO 記錄集中的更新提交到後臺數據庫是相當簡單的。假定視圖的屬性已經被設置正確了,那麼你只需要調用 TABLEUPDATE() 就可以了。在 ADO 的情況下,則調用 RecordSet.Update() 或者 UpdateBatch()。對於 SPT 和 XML,你就必須手工的做大量工作來把更新提交到後臺。
象我們前面看到的那樣,用 CursorAdapter 來提交更新只需要設置幾個屬性,然後就可以全部交給 VFP 去做其它所有的工作,或者你也可以很方便的通過指定怎麼刪除、插入和更新來獲得更大的靈活性。
×× 由於遠程視圖和 SPT 建立的結果集是 VFP 的 Cursor,所以你可以在VFP中的任何地方使用它們:表格、報表、用 Scan 來遍歷等等。而另一方面的 ADO 和 XML ,在使用之前就必須先把它們轉換成 Cursor,這會給你的應用程序增加額外的複雜性和處理它們的時間。
CursorAdapter 的結果集是一個 VFP Cursor,所以它有着與遠程視圖和SPT同樣的優勢。而更棒的是,即使數據源是 ADO 和 XML 你也能得到一個 VFP 的 Cursor,因爲 CursorAdapter 會自動爲你處理好轉換的事情併爲你形成一個 Cursor。
×× 由於一個遠程視圖的 SQL Select 語句是預先定義好的,所以你無法動態去修改它。儘管對於那些典型的數據輸入表單來說這已經足夠了,但是對於查詢和報表來說則不然。可能你必須要建立好幾個從同一個表中取得數據的視圖,每一個會選擇不同的字段、使用不同的 WHERE 子句結構等等。對於 SPT、ADO 或 XML 來說,這不是一個問題。
CursorAdapters 不會受這個問題的折磨——你可以很輕鬆的通過改動 SelectCmd 屬性來改變取得什麼數據以及怎麼取得數據。
×× 你不能從一個遠程視圖中調用存儲過程,所以遠程視圖需要直接訪問後臺的表。對於你的應用程序的數據庫管理員來說,這可能是一個問題——某些數據庫管理員認爲,基於安全的或者什麼其它原因,所有的數據訪問應該只通過存儲過程來進行。而且,由於存儲過程是在後臺預編譯的,所以它執行起來通常要比 SQL Select 語句快得多。在 SPT、ADO 和 XML 的情況下,你可以根據需要來調用存儲過程。
通過簡單的設置 SelectCmd 屬性,CursorAdapters 也可以使用存儲過程。
×× 遠程視圖保存在一個數據庫容器中,所以還有一套你必須維護和安裝到客戶系統上的文件。此外,當你打開一個視圖的時候,VFP 會試圖去鎖定在 DBC 中的視圖的記錄,雖然這只需要很短的時間。在一個忙碌的系統中,當幾個用戶試圖同時打開一個表單的時候,這可能會造成衝突。儘管也有一些變通的處理辦法(把DBC拷貝到本地工作站上、或者使用在 VFP 7 以上版本中的 SET REPROCESS SYSTEM 來減少鎖定的時間),這總是一件你要操心的事情。還有一個問題是:如果你使用一個 SELECT * 的視圖來從一個指定的表取得數據、而那個表的結構又在後臺被改動過了,那麼這個視圖就會變得無效而且必須要重建才能解決。對於 SPT、ADO 和 XML 來說,由於它們與 DBC 無關,因此它們都沒有這些問題。
因爲它們都不在 DBC 裏面,所以 CursorAdapters 就也沒這些問題了。
×× 由於遠程視圖和 SPT 是通過 ODBC 來工作的,因此它們只能用於直接數據連接的“客戶—服務器”模式。而ADO和XML有着在一個 N-層應用程序中的多個層之間傳遞數據的機制可供選擇。
由於 CursorAdapters 可以建立來自 ADO 或者 XML 數據的 VFP Cursor,因此它對於在一個 N-層應用程序中被用於用戶界面層來說是理想的。
總結
××
我認爲 CursorAdapters 是 VFP 8 中最重要、最令人興奮的增強之一,因爲它提供了一個對於遠程數據源的統一而又容易使用的接口,此外,象我們將要在以後的文章中講述的那樣,它還允許我們建立可重用的數據類。下個月我們將探討一下訪問本地數據或者使用 ODBC、ADO和XML來訪問遠程數據的細節。再下個月,我們將探討一下建立可重用數據類、以及怎樣在報表中使用 CursorAdapters。
【譯者注】
附帶的示例文件是一個PRG,實在太簡單了,我就直接把內容貼在這裏了。
local lcConnString, ;
loCursor as CursorAdapter, ;
laErrors[1]
lcConnString = 'driver=SQL Server;server=(local);database=Northwind;' + ;
      'uid=sa;pwd=;trusted_connection=no'
* change password to appropriate value for your database
loCursor = createobject('CursorAdapter')
with loCursor
  .Alias = 'Customers'
  .DataSourceType = 'ODBC'
  .DataSource = sqlstringconnect(lcConnString)
  .SelectCmd = "select CUSTOMERID, COMPANYNAME, CONTACTNAME " + ;
      "from CUSTOMERS where COUNTRY = 'Brazil'"
  .KeyFieldList = 'CUSTOMERID'
  .Tables = 'CUSTOMERS'
  .UpdatableFieldList = 'CUSTOMERID, COMPANYNAME, CONTACTNAME'
  .UpdateNameList = 'CUSTOMERID CUSTOMERS.CUSTOMERID, ' + ;
      'COMPANYNAME CUSTOMERS.COMPANYNAME, CONTACTNAME CUSTOMERS.CONTACTNAME'
  if .CursorFill()
     browse
  else
     aerror(laErrors)
     messagebox(laErrors[2])
  endif .CursorFill()
endwith

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章