VFP 愛用者社區 首頁 VFP 愛用者社區
本討論區為 Visual Foxpro 愛用者經驗交流的地方, 請多多利用"搜尋"的功能, 先查看看有無前例可循, 如果還有不懂的再發問. 部份主題有附加檔案, 須先註冊成為社區居民才可以下載.
 
 常見問題常見問題   搜尋搜尋   會員列表會員列表   會員群組會員群組   會員註冊會員註冊 
 個人資料個人資料   登入檢查您的私人訊息登入檢查您的私人訊息   登入登入 

CursorAdapter 起步1(轉貼)

 
發表新主題   回覆主題    VFP 愛用者社區 首頁 -> VFP 討論區
上一篇主題 :: 下一篇主題  
發表人 內容
Ruey



註冊時間: 2003-03-12
文章: 1698
來自: tunglo

第 1 樓

發表發表於: 星期五 八月 29, 2003 10:48 am    文章主題: CursorAdapter 起步1(轉貼) 引言回覆

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

CursorAdapter 類是 VFP 8 中最重要的新功能之一,因為它提供了一種簡單易用、介面統一的訪問遠端資料源方式。在這個月的文章堙ADung 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 變得非常容易,即時你不熟悉這些技術。
• 不管你選擇了哪種遠端資料源機制,它都提供一種統一的訪問介面。
• 從一種機制轉換到另一種機制變得非常的輕鬆。
這堿O上面的最後一個觀點的例子。假設你有一個使用 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
這個屬性堳O存的是 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 的事件發生(類似於資料庫事件)。
示例
這堿O一個示例來自附帶的示例檔 (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'
* 把這堛滷K碼改成你自己的資料庫中密碼

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 堶情A所以 CursorAdapters 就也沒這些問題了。
• 由於遠端視圖和 SPT 是通過 ODBC 來工作的,因此它們只能用於直接資料連接的“客戶—伺服器”模式。而ADO和XML有著在一個 N-層應用程式中的多個層之間傳遞資料的機制可供選擇。
由於 CursorAdapters 可以建立來自 ADO 或者 XML 資料的 VFP Cursor,因此它對於在一個 N-層應用程式中被用於用戶介面層來說是理想的。
總結

我認為 CursorAdapters 是 VFP 8 中最重要、最令人興奮的增強之一,因為它提供了一個對於遠端資料源的統一而又容易使用的介面,此外,象我們將要在以後的文章中講述的那樣,它還允許我們建立可重用的資料類。下個月我們將探討一下訪問本地資料或者使用 ODBC、ADO和XML來訪問遠端資料的細節。再下個月,我們將探討一下建立可重用資料類、以及怎樣在報表中使用 CursorAdapters。

【譯者注】
附帶的示例檔是一個PRG,實在太簡單了,我就直接把內容貼在這堣F。

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
CursorAdapter 起步(二)用 CursorAdapter 來取得和更新資料
作者:Dung Hennig
譯者:fbilo

在 VFP8 中新增的 CursorAdapter 基類提供一個統一、易用的資料介面。Doug Hennig 在這個月的文章中演示了怎樣使用 CursorAdapter 來訪問本地資料和 ODBC、ADO和XML這樣的遠端資料——討論了使用各種資料源相應的特殊要求和實現途徑。
正文:
如我在上一篇文章中所提到的那樣,在VFP8中一個最重要的、也是最精彩的新功能是新的 CursorAdapter 基類。在那篇文章中,我們研究了一下 CursorAdapter 的屬性、事件和方法,並討論了它相對于遠程視圖、SQL PassThrough(SPT)、ADO和XML的優勢。

在開始使用 CursorAdapter 之前,你需要根據要訪問的是本地資料還是通過ODBC、ADO或者XML的遠端資料源的不同,注意這個類所相應的不同的特殊要求。這個月的文章就講述了使用各種資料源的細節。
使用本地資料源
儘管我們很清楚 CursorAdapter 是試圖用來標準化和簡化對非VFP資料的訪問方式的,不過你還是可以把它當作是 Cursor 的代替品用它來訪問VFP資料:只要把它的 DataSourceType 屬性設置成 "Native"。為什麼要這麼做呢?因為你的應用程式將來可能會需要升遷——那時候你就可以把 DataSourceType 屬性設置成其他幾個選項之一(當然可能還需要修改其他幾個屬性,例如設置連接資訊等等),就能輕鬆的切換到另一種資料庫引擎,例如SQL Server。

當 DataSourceType 屬性的設置為 "Native" 的時候,VFP 會忽略它的 DataSource 屬性。SelectCmd 屬性必須是一個 SQL Select 語句(而不是一個 USE 命令或運算式),這就意味著你用 CursorAdapter 不是直接操作本地表而是操作一個類似於本地視圖那樣的東西。你還必須確保VFP能夠找到出現在那個 Select 語句中的任何表,因此,如果這些表不在當前路徑中,那麼你就需要設置一下路徑或者打開這些表所屬的資料庫。此外,就跟用視圖一樣,如果你想讓這個 Cursor 是可更新的,你還必須設置好那些與更新相關的屬性(KeyFieldList、Tables、UpdatableFieldlist 和 UpdateNameList)。

下面的例子(文章附件 NativeExample.prg)會用 VFP 示例資料庫中的 Customer 表建立一個可更新的 Cursor:

local loCursor as CursorAdapter, laErrors[1]
Open database (_samples + 'data\testdata')
with loCursor
.Alias = 'customercursor'
.DataSourceType = 'Native'
.SelectCmd = "Select CUST_ID, COMPANY, CONTACT FROM CUSTOMER " + ;
"WHERE COUNTRY = 'Brazil'"
.KeyFieldList = 'CUST_ID'
.Tables = 'CUSTOMER'
.UpdatableFieldList = 'CUST_ID, COMPANY, CONTACT'
.UpdateNamelist = 'CUST_ID CUSTOMER.CUST_ID, '+ ;
'COMPANY CUSTOMER.COMPANY, CONTACT CUSTOMER.CONTACT'

if .CursorFill()
browse
tableupdate(1)
else
aerror(laErrors)
messagebox(laErrors[2])
endif .CursorFill()
endwith

close databases all
使用 ODBC
ODBC 是 DataSourceType 屬性四種設置中最簡單的一種。把 DataSource 設置為一個打開了的 ODBC 連接控制碼、設置一下常用的屬性、然後調用 CursorFill 來取得資料。如果你設好了 KeyFieldList、Tables、UpdatableFieldList 和 UpdateNameList 屬性,VFP 會自動把你對資料的任何改動轉換成相應的 UPDATE、INSERT、和 DELETE 語句來把改動提交到後臺資料源。如果你想用的是一個存儲過程,那麼要相應的設置 *Cmd、*CmdDataSource 和 *CmdDataSourceType 屬性(* 代表 “Delete”、“Insert”或“Update”)。

這堿O附件 ODBCExample.prg 中的一個例子,它調用 Sql Server 自帶的 NorthWind 資料庫中的 CustOrderHist 存儲過程來取得銷售給某個客戶的單位產品總數。

local lcConnString, loCursor as CursorAdapter, laErrors[1]

lcConnString = 'driver=SQL Server;server=(local);database=Northwind;uid=sa;pwd=;"+ ;
"trusted_connection=no'

** 把上面連接字串中的密碼改成你的SQL Server 登錄的密碼

loCursor = createobject('CursorAdapter')
with loCursor
.Alias = 'Customerhistory'
.DataSourceType = 'ODBC'
.DataSource = SQLStringConnect(lcConnString)
.SelectCmd = "exec CustOrderhist 'ALFKI'"

if .CursorFill()
browse
else
aerror(laErrors)
messagebox(laErrors[2])
endif .CursorFill()
endwith
使用 ADO
與使用 ODBC 相比,使用 ADO 要多一些需要操心的事情:
• DataSource 必須被設置成一個 ADO RecordSet,而且這個 RecordSet 的 ActiveConnection 屬性需要被設置成一個打開了的 ADO Connection 物件。
• 如果你想要使用一個參數化查詢(與下載全部資料相比,這可能是更常用的方式),你必須把一個 ADO Command 物件作為第四個參數傳遞給 CursorFill 方法,而且這個 Command 物件的 ActiveConnection 屬性需要被設置成一個打開了的 ADO Connection 物件。VFP 會為你照顧好填充 Command 物件的參數化集合的事情(它通過分析 SelectCmd 來找出參數),不過參數所包含的值當然還是必須在有效取值範圍內的。
• 在資料環境中只有一個使用了 ADO 的 CursorAdapter 這樣的情況是比較簡單的:如果需要的話,你可以把 UseDEDataSource 屬性設置成 .T.,然後根據你的需要把資料環境的 DataSource 和 DataSourceType 屬性設置成 CursorAdapter。不過,如果資料環境中有多個 CursorAdapter 的話,這種辦法就無效了。原因是 DataEnvironment.DataSource 所引用的 ADO RecordSet 只能包含一個 CursorAdapter 的資料;當你為第二個 CursorAdapter 調用 CursorFill 方法的時候,會出現 “RecordSet is already open (RecordSet 記錄集已經打開)”的錯誤。所以,如果你的資料環境中有超過一個的 CursorAdapter,你必須要把 UseDEDataSource 設置成 .F.,並自行管理每個 CursorAdapter 的 DataSource 和 DataSourceType 屬性(或者你可以使用一個能夠管理這種情況的 DataEnvironment 的子類)。

附件 ADOExample.prg 中的示例代碼演示了怎樣借助一個 ADO Command 物件來取得資料。這個示例還演示了使用 VFP8 中新的結構化錯誤處理的功能。對 ADO Connection 物件的 Open 方法的調用被封裝在一個 TRY...CATCH...ENDTRY 語句中,以捕捉調用這個方法失敗的時候將會出現的 COM 錯誤。

local loConn as ADODB.Connection, ;
loCommand as ADODB.Command, ;
loException as Exception, ;
loCursor as CursorAdapter, ;
lcCountry, ;
laErrors[1]
loConn = createobject('ADODB.Connection')
with loConn
.ConnectionString = 'provider=SQLOLEDB.1;data source=(local);' + ;
'initial catalog=Northwind;uid=sa;pwd=dhennig;trusted_connection=no'
&& 把上面連接字串中的密碼改成你的SQL Server 登錄的密碼
try
.Open()
catch to loException
messagebox(loException.Message)
cancel
endtry
endwith
loCommand = createobject('ADODB.Command')
loCursor = createobject('CursorAdapter')
with loCursor
.Alias = 'Customers'
.DataSourceType = 'ADO'
.DataSource = createobject('ADODB.RecordSet')
.SelectCmd = 'select * from customers where country=?lcCountry'
lcCountry = 'Brazil'
.DataSource.ActiveConnection = loConn
loCommand.ActiveConnection = loConn
if .CursorFill(.F., .F., 0, loCommand)
browse
else
aerror(laErrors)
messagebox(laErrors[2])
endif .CursorFill(.F., .F., 0, loCommand)
endwith
使用 XML
用 CursorAdapter 來操作 XML 需要一些特殊的設置。下面是這些問題:
• DataSource 屬性被忽略;
• CursorSchema 屬性必須被填充好——即使你給 CursorFill 傳遞的第一個參數是 .F. 也一樣——否則將會出錯。
• SelectCmd 必須被設置成一個運算式,例如一個用戶自定義函數(UDF)或者物件方法名,該運算式能夠為 Cursor 返回 XML。
• 對 Cursor 的改動會被轉換成一個 DiffGram,它是“包含著被改動了的欄位或者記錄,在被改動之前、被改動之後的值”的XML,當需要更新的時候,它被放在 DiffGram 屬性中。
• 為了把資料更動回寫到資料源中去,UpdateCmdDataSourceType 屬性必須被設置為“XML”,並且 UpdateCmd 必須被設置成一個能夠處理提交更新任務的運算式(象前面一樣,這個運算式也是象一個 UDF 或者物件的方法)。你可能會需要把 “This.DiffGram”傳遞給那個 UDF,這樣它就可以把更新提交給後臺資料源。
這個 Cursor 所使用的 XML 原始檔案可能來自各種不同的地方。例如,你可以調用這樣一個UDF:它能用 CursorToXML()來把一個VFP Cursor 轉換成 XML,並返回結果:

use CUSTOMERS
cursortoxml('customers', 'lcXML', 1, 8, 0, '1')
Return lcXML

UDF 可以調用一個 Web Service,這個 Web Service 則返回一個 XML 結果集。這堿O一個例子,我建立了一個 Web Service 並註冊在我自己的系統上, 而智慧感知則為我生成了下面的代碼(具體的細節並不重要,它只是演示了一個 Web Service 的例子):

loWS = newobject("WSclient', home() + 'ffc\_webservices.vcx')
loWS.cWSName = 'dataserver web service'
loWS = loWS.SetupClient('http://localhost/' + ;
'SQDataServer/dataserver.WSDL', 'dataserver', ;
'dataserverSoapPort')
lcXML = loWS.GetCustomers()
Return lcXML

它能夠在一個 Web Server 上使用 SQLXML 3.0 去執行一個存儲在一個暫存檔案中的 SQL Server 2000 查詢(要瞭解關於 SQLXML 更多的資訊,請訪問 http://msdn.microsoft.com/並查找 SQLXML)。下面的代碼使用一個 MSXML2.XMLHTTP 物件通過 HTTP 從 Northwind 資料庫的 Customers 表來取得所有的記錄,稍後我們將做更進一步的解釋。

local loXML as MSXML2.XMLHTTP
loXML = createobject('MSXML2.XMLHTTP')
loXML.open('POST', 'http://localhost/northwind/' + ;
'template/getallcustomers.xml, .F.)
loXML.setRequestHeader('Content-type', 'text/xml')
loXML.send()
return loXML.responseText

處理更新的事情要更複雜一點。資料源必須或者能夠接受並處理一個 DiffGram (比如 SQL Server 2000 的情況),或者你必須自己去弄清楚所有的改動、執行一系列的 SQL 語句(UPDATE、INSERT 和 DELETE)去提交更新。

這堿O個使用了帶 XML 資料源的 CursorAdapter 的例子(XMLExample.prg)。要注意的是:SelectCMD 和 UpdateCMD 都是要調用 UDF 的。在 SelectCMD 的情況中,要返回資料的客戶編號被傳遞給一個叫做 GetNEWustomers 的 UDF,這個我們稍後再提。在 UpdateCmd 的情況中,VFP 把 DiffGram 屬性傳遞給 SendNWXML,這個我們也稍後再提。

local loCustomers as CursorAdapter, ;
laErrors[1]
loCustomers = createobject('CursorAdapter')
with loCustomers
.Alias = 'Customers'
.CursorSchema = 'CUSTOMERID C(5), COMPANYNAME C(40), ' + ;
'CONTACTNAME C(30), CONTACTTITLE C(30), ADDRESS C(60), ' + ;
'CITY C(15), REGION C(15), POSTALCODE C(10), COUNTRY C(15), ' + ;
'PHONE C(24), FAX C(24)'
.DataSourceType = 'XML'
.KeyFieldList = 'CUSTOMERID'
.SelectCmd = 'GetNWCustomers([ALFKI])'
.Tables = 'CUSTOMERS'
.UpdatableFieldList = 'CUSTOMERID, COMPANYNAME, CONTACTNAME, ' + ;
'CONTACTTITLE, ADDRESS, CITY, REGION, POSTALCODE, COUNTRY, PHONE, FAX'
.UpdateCmdDataSourceType = 'XML'
.UpdateCmd = 'SendNWXML(This.DiffGram)'
.UpdateNameList = 'CUSTOMERID CUSTOMERS.CUSTOMERID, ' + ;
'COMPANYNAME CUSTOMERS.COMPANYNAME, ' + ;
'CONTACTNAME CUSTOMERS.CONTACTNAME, ' + ;
'CONTACTTITLE CUSTOMERS.CONTACTTITLE, ' + ;
'ADDRESS CUSTOMERS.ADDRESS, ' + ;
'CITY CUSTOMERS.CITY, ' + ;
'REGION CUSTOMERS.REGION, ' + ;
'POSTALCODE CUSTOMERS.POSTALCODE, ' + ;
'COUNTRY CUSTOMERS.COUNTRY, ' + ;
'PHONE CUSTOMERS.PHONE, ' + ;
'FAX CUSTOMERS.FAX'
if .CursorFill(.T.)
browse
else
aerror(laErrors)
messagebox(laErrors[2])
endif .CursorFill(.T.)
endwith


這堿O GetNWCustomers 的代碼。它使用了一個 MSXML2.XMLHTTP 物件來訪問一個位於一個Web Server 上的名叫 CustomersByID.xml 的 SQL Server 2000 XML 範本,並返回結果。要獲取資料的 Customer ID 被作為一個參數傳遞給這段代碼:

lparameters tcCustID
local loXML as MSXML2.XMLHTTP
loXML = createobject('MSXML2.XMLHTTP')
loXML.open('POST', "http://localhost/northwind/template/customersbyid.xml?"; + ;
"customerid=" + tcCustID, .F.)
loXML.setRequestHeader('Content-type', 'text/xml')
loXML.send()
return loXML.responseText

這段代碼堣犍峈漲W為 CustomersByID.XML 的 XML 範本的內容如下:

<root xmlns:sql="urn:schemas-microsoft-com:xml-sql">
<sql:header>
<sql:param name="customerid">
</sql:param>
</sql:header>
<sql:query client-side-xml="0">
SELECT *
FROM Customers
WHERE CustomerID = @customerid
FOR XML AUTO
</sql:query>
</root>

把這個檔放在用於 Northwind 資料庫的一個虛擬目錄中(參見補充文檔《設置 SQL Server 2000 XML 訪問》以瞭解更多關於為 SQL Server 2000 設置 IIS 的內容、以及這篇文章所需要的特殊細節。)

SendNWXML 的內容看起來與 GetNWCustomers 類似,除了它接收的參數是一個 DiffGram,然後它把這個 DiffGram 載入到一個 MSXML2.DOMDocumnet 物件中,並把這個物件傳遞給 Web Server,該 Web Server 會通過 SQLXML 把這個物件傳遞給 SQL Server 2000 去處理。

lparameters tcDiffGram
local loDOM as MSXML2.DOMDocument, ;
loXML as MSXML2.XMLHTTP
loDOM = createobject('MSXML2.DOMDocument')
loDOM.async = .F.
loDOM.loadXML(tcDiffGram)
loXML = createobject('MSXML2.XMLHTTP')
loXML.open('POST', 'http://localhost/northwind/', .F.)
loXML.setRequestHeader('Content-type', 'text/xml')
loXML.send(loDOM)

運行 XMLExample.prg 來看看它是怎麼工作的。你將會在 Browse 視窗中看到一台記錄(客戶 ALFKI)。試著改動幾個欄位的值,然後關閉這個視窗,再運行 PRG 一遍。你會看到你的改動已經被寫入到後臺資料源中了。
總結
儘管 CursorAdapter 基類提供了一種對遠端資料源的統一的結構,而不管你使用的是 ODBC、ADO還是XML——但是,根據你選擇的資料訪問機制的不同,對 CursorAdapter 的設置也有一些區別。這些區別取決於資料訪問機制的本身。

下個月,我將通過建立一些可重用的資料類、並討論怎樣在報表中使用 CursorAdapter 來結束這個系列的專題。
補充文檔:
《設置 SQL Server 2000 XML 訪問》

為了能夠在一個流覽器或者其他 HTTP 用戶端用一個 URL 來訪問 SQL Server 2000,你需要做一些工作。首先,你需要從 MSDN 網站(http://msdn.microsoft.com/——查詢一下“SQLXML”,然後選擇下載)去下載和安裝 SQLXML 3.0。

接著,你需要設置一個 IIS 虛擬目錄。步驟如下:從開始功能表|程式|SQLXML 3.0檔夾中單擊“Configure IIS Support(設置 IIS 支援)”。展開你的伺服器節點,選擇要使用的 Web 站點,然後單擊滑鼠右鍵,選擇 “新建|虛擬目錄”,在出現的對話方塊的“常規”頁中輸入虛擬目錄的名稱和它的物理路徑。在這堙A我們使用“Northwind”作為虛擬目錄名、“NorthwindTemplates”作為物理路徑。使用 Windows 資源管理器在你的系統上的什麼地方建立這個物理目錄,然後給它建一個名為 “Template”的子目錄(稍後我們將會用到這個子目錄)。把附件中的兩個範本檔 GetAllCustomers.xml 和 CustomersByID.xml 拷貝到這個子目錄中。

在“安全”頁中,輸入訪問 SQL Server 的相應的資訊,例如用戶名和密碼或者你想採用的特定的驗證機制。在“資料源”頁上,選擇 SQL Server,如果需要的話,還要選擇要使用的資料庫。在這塈畯抰嚝 Northwind 資料庫。在“設置”頁上選擇希望的設置,至少要選上“允許範本查詢”和“允許 Post”。

在“虛擬名稱”頁中,從類型下拉式列示方塊中選擇“範本”,並輸入一個虛擬名稱(在這塈畯怢洏峞含emplate”)和物理路徑(它應該是虛擬目錄的一個子目錄,在這奡N是 "Template"子目錄),這是使用範本的需要。好,單擊“確定”。

現在我們測試一下是否每樣東西都設置正確了,我們將通過使用你拷貝到 Template 子目錄中去得 GetAllCustomers.xml來訪問 SQL Server。它的內容如下:

<root xmlns:sql="urn:schemas-microsoft-com:xml-sql">
<sql:query client-side-xml="0">
SELECT *
FROM Customers
FOR XML AUTO
</sql:query>
</root>

為了測試的目的,打開你的流覽器,並輸入這個URL:http://localhost/northwind/template/getallcustomers.xml,你就會在流覽器中看到XML形式的 Northwind Customers 表的內容了。

<root xmlns:sql='urns:schemas-micorsoft-com:xml-sql">
<Customers CustomerID="ALFKI" CompanyName="Alfreds Futterkiste" ContactName = "Maria Anders"
ContactTitle="Sales Represendative"
Address="Obere Str. 57" City="Berlin" PostalCode="12209"
Country="Germany" Phone="030-0074321"
Fax="999-999-9999" />

現在你就已經做好運行這篇文章的 SQLXML 示例的準備了。

示例文件:

點擊流覽該檔
CursorAdapter 起步(三)可重用資料類
作者:Doug Hennig
譯者:fbilo

VFP 的程式師們想要一個可重用的資料類已經很久了。儘管在過去的版本中也有許多解決這個問題的辦法,不過總是有點美中不足。現在在 VFP 8堙A我們有了真正的可重用資料類。這個月,Doug 為我們演示了怎樣通過建立 CursorAdapter 和 DataEnvironment 的子類來建立可重用的資料類、以及怎樣在表單和報表中使用它們。
正文
在過去的兩期雜誌中,我們討論了在 VFP 8 中新增的 CursorAdapter 基礎類。我個人的觀點是,這是 VFP 8 中最重要的改動之一,因為它向我們提供了一個物件SQL Server這樣的非VFP資料源的簡單易用、統一的介面。此外,如你本月所能見到的那樣,它們還形成了可重用資料類的基礎。

在講述可重用資料類之前,讓我們先來看一下我建立的一些 CursorAdapter 和 DataEnvironment 的子類,我給它們增加了一些額外的功能,它們將成為我們的可重用資料類的起點。
SFCursorAdapter
SFCursorAdapter (在附件 SFDataClasses.vcx 中) 是 CursorAdapter 的一個子類,它擁有一些額外增加的功能,如下:
• 它可以自動處理參數化查詢:你可以靜態(一個常量)也可以動態(一個運算式,例如“=Thisform.txtName.value”,當 Cursor 被打開或者刷新的時候,這個運算式會被運算)的定義一個參數值。
• 它可以在 Cursor 被打開以後自動在該 Cursor 上建立索引。
• 對於 ADO,它還會執行一些特殊的工作,例如把 DataSource 屬性設置為一個 ADO RecordSet,把這個 RecordSet 的 ActiveConnection 屬性設置為一個 ADO Connection 物件,當用到一個參數化查詢的時候,它還會建立一個 ADO Command 物件並把這個物件傳遞給 CursorFill 方法。
• 它提供了簡單的錯誤處理(cErrorMessage 屬性媟|有錯誤的資訊)。
• 它還有 CursorAdapter 中缺少的 Update 和 Release 方法。
這個類的 INIT 方法建立兩個集合(使用新的 Collection 基礎類,它是維護某些東西的集合用的),一個是為 SelectCmd 屬性可能會用到的參數而準備的,另一個是用於在 Cursor 被打開以後應該自動建立的標記。它還會 SET MULTILOCK ON,因為這是 CursorAdapter Cursor 的需求。

This.oParameters = CreateObject('Collection')
This.oTags = CreateObject('Collection')
Set multilocks on

AddParameter 方法象 parameters 集合添加一個參數。給這個方法傳遞參數的名稱(這個名稱應該與該參數出現在 SelectCmd 屬性中的那個名稱相一致),根據需要也可以付上參數的值(如果你現在不給它傳遞參數的值,也可以以後再調用 Getparameter 方法來傳遞)。這段代碼演示了一對 VFP 8 中的新功能:新的 empty 基礎類,它沒有任何屬性、事件或者方法,因此是建立一個羽量級的物件的理想選擇;還有 AddProperty() 函數,它的作用跟 AddProperty 方法類似,區別是它用於那些沒有這個方法的物件。

lparameters tcName, tuvalue
local loParameter
loParameter = createobject('Empty')
addproperty(loParmeter, 'Name', tcName)
addproperty(loParmeter, 'value', tuvalue)
This.oParameters.Add(loParameter, tcName)

使用 GetParmeter 方法來返回一個特殊的 parameter 物件——通常是用在需要設置用於參數的值的時候。

lparameters tcName
local loParameter
loParameter = This.oParameters.Item(tcName)
return loParameter

SetConnection 方法用於將 DataSource 屬性設置為希望的連接。如果 DataSourceType 是 “ODBC”,就給這個方法傳遞一個連接控制碼;如果是“ADO”,DataSource 必須是一個ADO Recordset 物件,而且該物件的 ActiveConnection 屬性必須要設置為一個活動 ADO Connection 物件,所以,我們需要向 SetConnection 方法傳遞這個 ADO Connection 物件, SetConnection 會建立一個 RecordSet,並且把這個 RecordSet 的 ActiveConnection 設置為被傳遞的 ADO Connection 物件。

lparameters tuConnection
with this
do case
case .DataSourceType = 'ODBC'
.DataSource = tuConnection
case .DataSourceType = 'ADO'
.DataSource = Createobject('ADODB.RecordSet')
.DataSource.ActiveConnection = tuConnection
endcase
endwith

為了建立 Cursor,我們調用 GetData 方法而不是 CursorFill 方法,因為 GetData 能夠自動處理參數和錯誤。如果你想要建立一個不帶資料的 Cursor,那麼就給 GetData 方法傳遞一個 .T.。這個方法建立的第一樣東西,是與定義在 parameters 集合中的參數們同名的私有變數(在這婼掍峇F GetParametervalue 方法,該方法會返回參數物件的值,如果該物件的值是一個以“=”開頭的運算式,那麼返回的將是運算該運算式之後所獲得的值。)下一步,如果我們是在使用 ADO 並且已經有了一些參數,這段代碼會建立一個 ADO Command 物件,並把該物件的 ActiveConnection 屬性設置為 Connection 物件,然後把這個 Connection 物件傳遞給 CursorFill 方法——這是 CursorAdapter 處理 ADO 參數化查詢的需要。如果我們不是在用 ADO 、或者沒有任何參數,那麼代碼會簡單的調用 CursorFill 來填充 Cursor。注意,如果給 GetData 方法傳遞了 .T.,並且 CursorSchema 已經被填寫好了,那麼就是告訴 GetData 方法去使用 CursorSchema(這也是我想讓 CursorAdapter 基類擁有的功能)。現在如果 Cursor 被建立起來了,代碼會調用 GreateTags 方法來為該 Cursor 建立想要的索引;如果 Cursor 沒有被建好,那麼它會調用 HandleError 方法來處理任何發生了的錯誤。

lparameters tlNoData
local loParameter, ;
lcName, ;
luvalue, ;
llUseSchema, ;
loCommand, ;
llReturn
with This

* tlNoData參數指定是否要向 Cursor 中填充資料,如果要填充的話,
* 我們就要把建立存儲所有參數的變數的活在這奡N做掉而不是放到一個其他的方法中去。
* 因為我們希望這些變數的有效範圍是私有的。

if not tlNoData
for each loParameter in .oParameters
lcName = loParameter.Name
luvalue = .GetParametervalue(loParameter)
store luvalue to (lcName)
next loParameter
endif not tlNoData

* 如果我們正在使用 ADO,並且有了一些參數,那麼就需要有一個處理這些參數的 Command物件

llUseSchema = not empty(.CursorSchema)
if '?' $ .SelectCmd and (.DataSourceType = 'ADO' or (.UseDEDataSource and ;
.Parent.DataSourceType = 'ADO'))
loCommand = createobject('ADODB.Command')
loCommand.ActiveConnection = iif(.UseDEDataSource, ;
.Parent.DataSource.ActiveConnection, .DataSource.ActiveConnection)
llReturn = .CursorFill(llUseSchema, tlNoData, .nOptions, loCommand)
else

* 填充這個 cursor.

llReturn = .CursorFill(llUseSchema, tlNoData, .nOptions)
endif '?' $ .SelectCmd ...

* 如果 Cursor 建立成功,則為之定義所有的 Tag,否則則處理發生的錯誤。

if llReturn
.CreateTags()
else
.HandleError()
endif llReturn
endwith
return llReturn

還有一些方法這塈畯抴N不說了,你可以自己去研究它們。HandleError 方法使用 Aerror() 來判斷發生了什麼錯誤,並把錯誤陣列的第二個元素放到 cErrorMessage 屬性中去。Requery 方法與 GetData 類似,不過它是用來刷新 Cursor 中的資料。調用這個方法而不是 CursorRefresh 方法的原因就象 GetData 一樣:它能夠處理參數和錯誤。Update 方法很簡單:它就是調用 TableUpdate() 來提交當前資料源的更新,如果提交更新失敗,則調用 HandleError 方法來處理錯誤。AddTag 用於在 Cursor 被建立後將你想要建立的索引的資訊添加到 Tags 集合中,而 GetData 方法會調用的 CreateTags 方法則會在自己的 Index ON 語句中用到這個集合中的資訊。

這堿O使用這個類的一個例子,是從附件中的 TestCursorAdapter.prg 中拿來的。它從 SQL Server 自帶的 Northwind 資料庫的 Customers 表中取得資料。它的 SelectCmd 屬性堿O一個參數化查詢的 Select 語句,向你演示了怎樣用 AddParameter 方法來處理參數,以及怎樣用 AddTag 方法來自動地為 Cursor 建立索引標識。

local loCursor as SFCursorAdapter of SFCursorAdapter, ;
loConnMgr as SFConnectionMgrODBC of SFRemote, ;
loParameter as Empty
lnHandle = sqlstringconnect('driver=SQL Server;server=(local);' + ;
'database=Northwind;uid=sa;pwd=;trusted_connection=no')
&& change password to appropriate value for your database
loCursor = newobject('SFCursorAdapter', 'SFDataClasses')
with loCursor
.DataSourceType = 'ODBC'
.Alias = 'Customers'
.SelectCmd = 'select * from customers where country = ?pcountry'
.SetConnection(lnHandle)
.AddParameter('pcountry', 'Brazil')
.AddTag('CustomerID', 'CustomerID')
.AddTag('Company', 'upper(CompanyName)')
.AddTag('Contact', 'upper(ContactName)')
if .GetData()
messagebox('Brazilian customers in CustomerID order')
set order to CustomerID
go top
browse
messagebox('Brazilian customers in Contact order')
set order to Contact
go top
browse
messagebox('Canadian customers')
loParameter = .GetParameter('pcountry')
loParameter.value = 'Canada'
.Requery()
browse
else
messagebox('Could not get the data. The error message was:' + ;
chr(13) + chr(13) + .cErrorMessage)
endif .GetData()

* Now try to do an invalid SELECT statement.

.SelectCmd = 'select * from customersx'
if .GetData()
browse
else
messagebox('Could not get the data. The error message was:' + ;
chr(13) + chr(13) + .cErrorMessage)
endif .GetData()
endwith
sqldisconnect(lnHandle)
SFDataEnvironment
在附件 SFDataClasses.vcx 中的這個資料和環境類要比 SFCursorAdapter 簡單的多。但它增加了一些非常有用的功能:
• GetData 方法會調用所有在這個資料環境類堶悸 SFCursorAdapter 成員類的 GetData 方法,這樣你就不需要自己去一個個的調用它們。與此類似的是,Requery 方法和 Update 方法也會調用每個 SFCursorAdapter 成員類的 Requery 和 Update 方法。
• 象 SFCursorAdapter 一樣,SetConnection 方法會把 DataSource 設置為一個 ADO Recordset,並把這個 Recordset 的 ActiveConnection 屬性設置為一個 ADO Connection 物件。不過,它還會調用所有 UseDEDataSource 屬性被設置為 .F. 的 SFCursorAdapter 成員類的 SetConnection 方法。
• 它提供了一些簡單的錯誤處理(cErrorMessage 屬性會被填入錯誤資訊)
• 它有一個 Release 方法。
現在我們看看這個類的一對方法。GetData 非常簡單:如果這個資料環境類的任何成員物件擁有 GetData 方法,則調用該方法:

lparameters tlNoData
local loCursor, ;
llReturn
for each loCursor in This.Objects
if pemstatus(loCursor, 'GetData', 5)
llReturn = loCursor.GetData(tlNoData)
if not llReturn
This.cErrorMessage = loCursor.cErrorMessage
exit
endif not llReturn
endif pemstatus(loCursor, 'GetData', 5)
next loCursor
return llReturn

SetConnection 方法稍微複雜一點:如果它的任何成員物件有 SetConnection 方法、並且該成員物件的 UseDEDataSource 屬性被設置為 .F.,則調用該成員物件的 SetConnection 方法;然後,如果有任何一個 CursorAdapter 物件的 UseDEDataSource 屬性被設置為了 .T.,則使用類似於 SFCusrorAdapter 中的那樣的代碼來設置自己的 DataSource:

lparameters tuConnection
local llSetOurs, ;
loCursor, ;
llReturn
with This

* Call the SetConnection method of any CursorAdapter that isn't using our
* DataSource.

llSetOurs = .F.
for each loCursor in .Objects
do case
case upper(loCursor.BaseClass) <> 'CURSORADAPTER'
case loCursor.UseDEDataSource
llSetOurs = .T.
case pemstatus(loCursor, 'SetConnection', 5)
loCursor.SetConnection(tuConnection)
endcase
next loCursor

* If we found any CursorAdapters that are using our DataSource, we'll need to
* set our own DataSource.

if llSetOurs
do case
case .DataSourceType = 'ODBC'
.DataSource = tuConnection
case .DataSourceType = 'ADO'
.DataSource = createobject('ADODB.RecordSet')
.DataSource.ActiveConnection = tuConnection
endcase
endif llSetOurs
endwith

TestDE.prg 做了一個演示,它使用 SFDataEnvironment 作為容器,該容器中有一對 SFCursorAdapter 類。因為這個例子用的是 ADO,所以每個 SFCursorAdapter 都需要它自己的 DataSource,因此它們的 UseDEDataSource 屬性都被設置成了 .F.(默認設置)。注意:只要簡單的調用一下 SFDataEnvironment 的 SetConnection 方法就能搞定為每個 CursorAdapter 設置好 DataSource 屬性的事情。

local loConn as ADODB.Connection
loConn = createobject('ADODB.Connection')
loConn.ConnectionString = 'provider=SQLOLEDB.1;data source=(local);' + ;
'database=Northwind;uid=sa;pwd='
&& change password to appropriate value for your database
loConn.Open()
set classlib to SFDataClasses
loDE = createobject('SFDataEnvironment')
with loDE
.AddObject('CustomersCursor', 'SFCursorAdapter')
with .CustomersCursor
.Alias = 'Customers'
.SelectCmd = 'select * from customers'
.DataSourceType = 'ADO'
endwith
.AddObject('OrdersCursor', 'SFCursorAdapter')
with .OrdersCursor
.Alias = 'Orders'
.SelectCmd = 'select * from orders'
.DataSourceType = 'ADO'
endwith
.SetConnection(loConn)
if .GetData()
select Customers
browse nowait
select Orders
browse
else
messagebox('Could not get the data. The error message was:' + ;
chr(13) + chr(13) + .cErrorMessage)
endif .GetData()
endwith
loConn.Close()
可重用資料類
現在我們已經有了可用的 CursorAdapter 和 DataEnvironment 的子類,讓我們來談談可重用資料類的事情。

一件 VFP 程式師們已經向 Microsoft 要求了很久的事情是可重用資料類。例如,你可能有一個表單和一個報表,它們使用的是完全相同的一套資料,然而你卻不得不重複的去手動向資料環境中添加一個個表或者視圖——因為資料環境是不可重用的。某些程式師(以及幾乎所有的應用程式框架提供商)採用了通過代碼來建立可重用資料環境(那時候資料環境是不能視覺化的建立子類的)的方法,並且在表單上使用一個 “loader”物件來建立 DataEnvironment 子類的實例。不管怎麼說,這種方法總不是那麼正規,並且無法用於報表。

現在,在 VFP8 堙A我們不僅擁有了建立“能夠向任何需要資料的物件提供來自任何資料源的資料”的可重用資料類的能力,還有了建立“能夠寄宿資料類的”資料環境類的能力。在我寫這篇文章的時候,你還不能在報表中使用 CursorAdapter 或者 DataEnvironment 的子類,不過你可以編程的添加 CursorAdapter 子類(例如把這些代碼寫在 DataEnvironment 的 INIT 方法中)來利用可重用類的優點。

讓我們為 Northwind 資料庫的 Customers 和 Orders 表建立一些資料類。CustomersCursor (在 NorthwindDataClasses.vcx 中)是 SFCursorAdapter 的一個子類,其屬性如表1:

表 1. CustomersCursor 的屬性
屬性 值
Alias Customers
CursorSchema CUSTOMERID C(5), COMPANYNAME C(40), CONTACTNAME C(30), CONTACTTITLE C(30), ADDRESS C(60),
CITY C(15), REGION C(15), POSTALCODE C(10), COUNTRY C(15), PHONE C(24), FAX C(24)
KeyFieldList CUSTOMERID
SelectCmd select * from customers
Tables CUSTOMERS
UpdatableFieldList CUSTOMERID, COMPANYNAME, CONTACTNAME, CONTACTTITLE, ADDRESS, CITY, REGION, POSTALCODE, COUNTRY, PHONE, FAX
UpdateNameList CUSTOMERID CUSTOMERS.CUSTOMERID, COMPANYNAME CUSTOMERS.COMPANYNAME, CONTACTNAME CUSTOMERS.CONTACTNAME, CONTACTTITLE CUSTOMERS.CONTACTTITLE, ADDRESS CUSTOMERS.ADDRESS, CITY CUSTOMERS.CITY, REGION CUSTOMERS.REGION, POSTALCODE CUSTOMERS.POSTALCODE, COUNTRY CUSTOMERS.COUNTRY, PHONE CUSTOMERS.PHONE, FAX, CUSTOMERS.FAX

你不會以為我會是手動在屬性視窗中輸入所有這些屬性的值吧?當然不是!我是用 CursorAdapter 的生成器來幹的。這堛漣犍帕O打開“Use connection settings in builder only(只使用在生成器中的連接設置)”,填入連接資訊以獲得一個活動連接,再填好 SelectCMD 以後,最後再用生成器來生成其他的屬性設置。

現在,任何時候你需要 Northwind 的 Customers 表中的資料,只要簡單的放一個 CustomersCursor 類就夠了。當然,我們還沒有定義任何連接資訊,不過做到這樣就已經很不錯了,有了這個類就不需要擔心怎麼獲得資料(ODBC、XML、ADO)、使用哪種資料引擎(比如 SQL Server、Access 以及 VFP 8中都有 Northwind 資料庫)之類的事情了。

不過,要注意的是這個 Cursor 對付的是 Customers 表中所有的記錄。可有時候,你又只想要一個客戶的資料。那麼,CustomerByIDCursor 是 CustomersCursor 的一個子類,它的 SelectCmd 已經被改成 “Select * from customers where customerid = ?pcustomerid”,還有下面增加的 INIT 方法的代碼:

lparameters tcCustomerID
dodefault()
This.AddParmeter('pCustomerID', tcCustomerID)

這段代碼會建立一個叫做 pCustomerID 的參數(它跟在 SelectCmd 中指定的是同一個),並且被設置成傳遞進來的任何值。如果沒有值被傳遞進來的話,那麼使用 GetParameter 方法來為這個參數返回一個物件,並在調用 GetData 之前設置它的 value 屬性。

OrdersCursor 類類似於 CustomersCursor,只是它返回的是 Orders 表中的所有資料,而 OrdersForCustomerCursor 是它的一個子類,用於返回一個指定客戶的所有訂單。

要測試一下的話,請運行 TestCustomersCursor.prg,它會從 SQL Server 版本的 Northwind 資料庫中 Customers 表的一個客戶,然後做到 Access 版本的 Northwind 資料庫所做的同樣的事情。這個示例演示了怎樣不為類指定連接資訊,這個類自己就能靈活的完成任務,因此,它的可重用性是很強的。
示例:表單
現在我們已經有了一些可重用類,讓我們來用用它們。首先,我們來建立 SFDataEnvironment 的一個子類 CustomersAndOrdersDataEnvironment (哈哈,名字可有夠長的,D.H牌冰糖葫蘆!),它包含著一個 CustomerByIDCursor 類和一個 OrdersForCustomerCursor 類。由於我們希望在打開表之前設置連接資訊,因此把它的 AutoOpenTables 屬性設置為了 .F.,而且把前面兩個 CursorAdapter 的 UseDEDataSource 屬性都設置為了 .T.。現在,這個資料環境類已經可以被用來在某個表單中顯示關於一個指定客戶的資訊以及他的訂單了。

讓我們來建立這麼一個表單。附件中的 CustomerOrders.scx 表單的 DEClass 和 DEClassLibrary 屬性已經被設置為了CustomersAndOrdersDataEnvironment 和 NorthwindDataClasses.vcx,這樣就用上了我們的可重用資料環境類。這個表單的 Load 方法堶悸漸N碼有點多,不過這是因為它要支持 ADO、ODBC、以及 XML 資料源,並且它還要建立自己的連接,所以大多數代碼都是處理這些問題的。如果它只支援一種資料源的話,比如只用 ODBC,再比如由另一個物件來管理連接(實際的應用程式開發中常常就是這樣的),代碼就會簡單多了:

with This.CustomersAndOrdersDataEnvironment
* 獲得連接
lnHandle = oApp.oConnectionMgr.GetConnectionHandle()
.SetConnection(lnHandle)

* 指定從 CustomerID 文本框中取得 cursor 參數的值
loParameter = ;
.CustomerByIDCursor.GetParameter('pCustomerID')
loParameter.value = '=Thisform.txtCustomerID.value'
loParameter = ;
.OrdersForCustomerCursor.GetParameter('pCustomerID')
loParameter.value = '=Thisform.txtCustomerID.value'

* 建立一個空的 cursor,如果失敗的話則顯示錯誤資訊

if not .GetData(.T.)
messagebox(.cErrorMessage)
return .F.
endif not .GetData(.T.)
endwith

這段代碼用上了那兩個 CursorAdapter 物件的 GetParameter 方法來把 pCustomerID 參數設置為表單上一個文本框中的值。注意在值堶悼峔鴘'=',它表示在你需要 value 屬性的時候再去運算它的值,所以我們實際上有了一個動態的參數(這樣就順應了當用戶在文本框中輸入了新的值以後要將改動反應到參數中去的需要)。調用 GetData 方法是為了建立一個空的 Cursor,這樣才能安頓那些資料綁定的控制項。

txtCustomerID 文本框沒有綁定什麼資料。它的 Valid 方法先調用資料環境的 Requery 方法,然後再調用表單的 Refresh 方法。這樣,當用戶輸入一個新的客戶ID的時候,就能夠 Requery 那個 Cursor,接著表單上其他控制項也會被刷新。表單上的其他文本框被綁定到由 CustomersByIDCursor 物件建立的 Customers cursor 的欄位中。那個 Grid 被綁定到由 OrdersForCustomerCursor 物件建立的 Orders Cursor。

運行這個表單,並輸入一個 Customer ID 為“ALFKI”(見圖1)。當你按下 Tab 鍵跳出這個文本框的時候,你會看到該客戶的位址資訊以及他的訂單就出現了。試著改動一些這個客戶的資料或者訂單資料,然後關閉表單再打開,再輸入一次“ALFKI”,你會看到你沒做什麼工作這些改動就都已經被寫到後臺資料庫中了。




圖1、

酷吧,嗯?跟建立一個基於本地表或者視圖的表單相比,並沒多多少工作。更棒的是:試試把定義在 Load 方法中的 ccDATASOURCETYPE 常量改變為 “ADO”或者“XML”,然後這個表單無論是看起來還是實際工作起來都跟沒改過之前一摸一樣(如果你想用 XML,你需要象上個月的文章中所說的那樣為 Northwind 資料庫設置一個 SQLXML 虛擬目錄,並把本月附件中的 XML 範本檔拷貝到那個目錄)。
示例:報表
我們來試試報表。這堻怳j的問題是:與表單不同,我們既不能告訴報表去使用一個資料環境子類,也不能拖放一個 CursorAdapter 子類到資料環境中去。所以我們不得不向報表放入一些代碼以將 CursorAdapter 添加到資料環境。儘管從邏輯上來看應該把這些代碼放到報表資料環境的 BeforeOpernTables 事件中去,不過事實上這樣做卻是行不通的,因為——由於某些我不能理解的原因—— BeforeOpenTables 事件只會在你預覽報表的每一頁的時候才會觸發。所以,我們只好把代碼放在 Init 方法堙C因為演示的需要,報表 CustomerOrders.frx 跟表單 CustomerOrders.scx 一樣,要比實際開發的情況下會用到的代碼更複雜一些。如果沒有這些演示的需求的話,實際上可以簡化到下面這樣:

with This
set safety off

* 獲得連接

.DataSource = oApp.oConnectionMgr.GetConnectionHandle()

* 建立客戶和訂單的 CursorAdapter 物件

.NewObject('CustomersCursor', 'CustomersCursor', ;
'NorthwindDataClasses')
.CustomersCursor.AddTag('CustomerID', 'CustomerID')
.CustomersCursor.UseDEDataSource = .T.
.NewObject('OrdersCursor', 'OrdersCursor', ;
'NorthwindDataClasses')
.OrdersCursor.AddTag('CustomerID', 'CustomerID')
.OrdersCursor.UseDEDataSource = .T.

* 取得資料,如果失敗,則顯示錯誤資訊

if not .CustomersCursor.GetData()
messagebox(.CustomersCursor.cErrorMessage)
return .F.
endif not .CustomersCursor.GetData()

if not .OrdersCursor.GetData()
messagebox(.OrdersCursor.cErrorMessage)
return .F.
endif not .OrdersCursor.GetData()

* 從 Customers 設置一個與 Orders 的關係
set relation to CustomerID into Customers
endwith

這堛漸N碼比表單示例的要多一些,這是因為我們不能使用自定義的資料環境類導致不得不自己手動編碼來代替。

現在,我們怎樣才能盡可能簡單的就把那些欄位放到報表上去呢?由於 CursorAdapter 物件是我們用代碼在運行時才添加到資料環境中去的,在設計時就沒辦法享受到拖放欄位到報表上的方便了。這埵陪茪p技巧:建立一個會建立這些 Cursor 的 PRG檔,並讓這些 Cursor 處於有效範圍內(可以採用掛起 PRG 的運行或者把 CursorAdapter 物件聲明成 Public 的辦法),然後使用快速報表功能來把那些欄位放到報表上,這樣報表控制項的大小也設置好了。

圖2展示了當你預覽報表的時候該報表的情況。如果結合表單一起使用的話,你可以試試改動表單數據環境中的 #DEFINE 語句來換用其他類型的資料源。

 
總結
我們對新的 CursorAdapter 基礎類的研究就到這堣F。我個人對 CursorAdapter 的出現感到非常的興奮,並計畫給我的應用程式框架中的資料處理部件升升級以更充分的利用它的優點。

下個月,我們將研究一下在 VFP 8 中通過 Try ... Catch ... Finally ... EndTry 命令大大增強了的錯誤處理方式以及新的 Exception 基礎類。

_________________
#############################
快樂媽咪系列幸福宅配,喝十全雞湯~原來幸福那麼簡單!!

學會VFP使用者社區的搜尋,Code才會更有趣~
#############################
回頂端
檢視會員個人資料 發送私人訊息
從之前的文章開始顯示:   
發表新主題   回覆主題    VFP 愛用者社區 首頁 -> VFP 討論區 所有的時間均為 台北時間 (GMT + 8 小時)
1頁(共1頁)

 
前往:  
無法 在這個版面發表文章
無法 在這個版面回覆文章
無法 在這個版面編輯文章
無法 在這個版面刪除文章
無法 在這個版面進行投票
無法 在這個版面附加檔案
無法 在這個版面下載檔案


Powered by phpBB © 2001, 2005 phpBB Group
正體中文語系由 phpbb-tw 維護製作