VFP的數據策略:高級篇

VFP的數據策略:高級篇html

做者:Doug Hennig  翻譯:老瓷node

引語

在「VFP中的數據策略:基礎篇」一文中,咱們研究了VFP應用程序中訪問非VFP數據(如SQL Server)的不一樣機制:遠程視圖、SQL Passthrough、ADO、XML和VFP 8中添加的CursorAdapter類。在本文中,咱們將更詳細地討論CursorAdapter,並討論可重用數據類的概念。此外,咱們將簡要介紹新的XMLAdapter基類,並瞭解它如何幫助與其餘源(如ADO.NET)交換數據。web

CursorAdapter

在我看來,CursorAdapter是VFP 8中最大的新特性之一。我以爲他們這麼酷的緣由是:sql

  • 使得使用ODBC、ADO或XML變得容易,即便您不太熟悉這些技術。
  • 爲遠程數據提供了一致的接口,而無論您選擇何種機制。
  • 使從一種機制切換到另外一種機制變得容易。

最後是一個例子。假設您有一個應用程序使用帶有CursorAdapter的ODBC來訪問SQL Server數據,出於某種緣由,您但願更改成使用ADO相反。您只需更改CursorAdapters的DataSourceType並更改到後端數據庫的鏈接,就完成了。應用程序中的其餘組件既不知道也不關心這一點;它們仍然看到同一個遊標,而無論用於訪問數據的機制如何。數據庫

讓咱們開始經過查看CursorAdapter的屬性、事件和方法(PEMs)來檢查它們。編程

PEMS

這裏咱們不討論CursorAdapter類的全部屬性、事件和方法,只討論更重要些的屬性、事件和方法。有關完整列表,請參閱VFP文檔。
(PEMS:屬性、事件、方法統稱的縮寫——譯者注)後端

DataSourceType

這個屬性很重要:它決定了類的行爲,以及將什麼類型的值放入其餘一些屬性中。有效的選項是「Native」,這表示您使用的是Native表,或者是選擇「ODBC」、「ADO」或「XML」,這表示您使用了適當的機制來訪問數據。您可能不會使用「Native」,由於您可能會使用Cursor對象而不是CursorAdapter,但此設置將使之後升遷應用程序更容易。數組

DataSource

這是訪問數據的方法。當DataSourceType設置爲「Native」或「XML」時,VFP忽略此屬性。對於ODBC,將DataSource設置爲有效的ODBC鏈接句柄(這意味着您必須本身管理鏈接)。對於ADO,數據源必須是一個ADO記錄集,該記錄集的ActiveConnection對象設置爲打開的ADO鏈接對象(一樣,您必須本身管理)。服務器

UseDEDataSource

若是此屬性設置爲.T(默認值爲.F),則能夠不使用DataSourceType和DataSource屬性,由於CursorAdapter將使用數據環境(DataEnvironment)的屬性(VFP 8也將DataSourceType和DataSource添加到DataEnvironment類)。將此設置爲.T.的一個示例是,但願數據環境中的全部CursorAdapter使用相同的ODBC鏈接。併發

SelectCmd

對於除了XML之外的全部內容,這是用於檢索數據的SQL SELECT命令。對於XML,這能夠是能夠轉換爲遊標的有效XML字符串(使用內部XMLTOCURSOR()調用)或返回有效XML字符串的表達式(如UDF)。

CursorSchema

此屬性保存遊標的結構,其格式與您在CREATE Cursor命令中使用的格式相同(此類命令中括號之間的全部內容)。這裏有一個例子:CUST_ID C(6),COMPANY C(30),CONTACT C(30),CITY C(25)。儘管能夠將此項留空,並告訴CursorAdapter在建立遊標時肯定結構,但若是將CursorSchema填充進來,效果會更好。首先,若是CursorSchema爲空或不正確,則在打開窗體的數據環境時可能會出錯,或者沒法將字段從CursorAdapter拖放到窗體以建立控件。幸運的是,VFP附帶的CursorAdapter構建器能夠自動爲您填充這個內容。

AllowDelete, AllowInsert, AllowUpdate, and SendUpdates

這些屬性(默認爲.T)決定是否能夠執行刪除、插入和更新,以及是否將更改發送到數據源。

KeyFieldList, Tables, UpdatableFieldList, and UpdateNameList

若是但願VFP使用遊標中所作的更改自動更新數據源,則須要這些屬性,這些屬性的用途與視圖的同名CursorSetProp()屬性相同。KeyFieldList是一個逗號分隔的字段列表(不帶別名),這些字段構成遊標的主鍵。表是一個逗號分隔的表列表。UpdateableFieldList是一個逗號分隔的字段列表(沒有別名),能夠更新。UpdateNameList是一個逗號分隔的列表,它將遊標中的字段名與表中的字段名相匹配。UpdateNameList的格式以下:CursorFieldName1 Table.FieldName1,CursorFieldName2 Table.FieldName2……請注意,即便UpdateableFieldList不包含表的主鍵的名稱(由於您不但願更新該字段),它也必須仍然存在於UpdateNameList中,不然更新將不起做用。

*Cmd, *CmdDataSource, *CmdDataSourceType

若是要特別控制VFP如何刪除、插入或更新數據源中的記錄,能夠爲這些屬性集指定適當的值(將上面的*替換爲Delete、Insert和Update)。

CursorFill(UseCursorSchema, NoData, Options, Source)

此方法建立遊標並用數據源中的數據填充它(儘管能夠經過.T.使NoData參數建立空遊標)。對於第一個使用CursorSchema或.F中定義的模式的參數,傳遞.T。以從數據源建立適當的結構(在我看來,這種行爲是相反的)。必須設置多鎖,不然此方法將失敗。若是CursorFill因爲任何緣由失敗,它將返回.F,而不是引起錯誤;使用AERROR()來肯定出了什麼問題(儘管準備好進行一些深挖,由於您常常收到的錯誤消息不夠具體,沒法確切地告訴您問題是什麼)。

CursorRefresh()

此方法相似於ReQuery()函數:它刷新遊標的內容。

Before*() and After*()

幾乎每一個方法和事件都有先後「鉤子」事件,容許您自定義CursorAdapter的行爲。例如,在AfterCursorFill中,能夠爲遊標建立索引,使其始終可用。對於Before事件,能夠返回.F.以防止觸發它的操做發生(這與數據庫事件相似)。

下面是一個示例(CursorAdapterExample.prg),它從SQL Server附帶的Northwind數據庫的Customers表中獲取巴西客戶的某些字段。遊標是可更新的,所以若是您在遊標中進行了更改,請將其關閉,而後再次運行程序,您將看到您的更改已保存到後端。

local loCursor as CursorAdapter, ; 
  laErrors[1] 
loCursor = createobject('CursorAdapter') 
with loCursor 
  .Alias              = 'Customers' 
  .DataSourceType     = 'ODBC' 
  .DataSource         = sqlstringconnect('driver=SQL Server;' + ; 
    'server=(local);database=Northwind;uid=sa;pwd=;trusted_connection=no') 
  .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、Form類及其設計器進行了一些更改。
首先,如前所述,DataEnvironment類如今有DataSource和DataSourceType屬性。它自己不使用這些屬性,但已將UseDataSource設置爲.T.的任何CursorAdapter成員都使用這些屬性。其次,如今可使用類設計器(woo-hoo!)可視化地建立DataEnvironment子類。
至於表單,如今能夠經過設置新的DEClass和DEClassLibrary屬性來指定要使用的DataEnvironment子類。若是您這樣作,您對現有數據環境所作的任何事情(遊標、代碼等)都將丟失,但至少您會首先收到警告。表單的一個很酷的新特性是BindControls屬性;在屬性窗口中將其設置爲.F. 意味着VFP不會在初始化時嘗試對控件進行數據綁定,只有在將BindControls設置爲.T.時纔會這樣作。這有什麼好處?好吧,您詛咒參數傳遞給Init多少次了,Init在全部控件初始化並綁定到它們的ControlSource以後觸發?若是要將參數傳遞給告訴它要打開哪一個表的窗體或其餘影響ControlSources的內容,該怎麼辦?這個新屬性使這個問題很快解決。

其餘變化

CursorGetProp('SourceType')返回一個新的值範圍:若是遊標是用CursorFill建立的,則該值爲100加上舊值(例如,遠程數據爲102)。若是遊標是用CursorAttach建立的(容許您將現有遊標附加到CursorAdapter對象),則該值爲200加上舊值。若是數據源是ADO記錄集,則值爲104(CursorFill)或204(CursorAttach)。

生成器

VFP包括DataEnvironment和CursorAdapter構造器(或稱爲生成器——譯者注),使得使用這些類更加容易。
以正常方式啓動DataEnvironment Builder:在類設計器中右鍵單擊窗體的DataEnvironment或DataEnvironment子類,而後選擇Builder。數據環境生成器的「數據源」頁是設置數據源信息的位置。選擇所需的數據源類型和數據源的來源。若是選擇「使用現有鏈接句柄」(ODBC)或「使用現有ADO記錄集」(ADO),請指定包含數據源的表達式(例如「goConnectionMgr.nHandle」)。您還能夠選擇使用系統上的任一個DSN或鏈接字符串。只有在爲ADO選擇「使用鏈接字符串」時纔會啓用「生成」按鈕,該按鈕將顯示「數據連接屬性」對話框,您可使用該對話框直觀地生成鏈接字符串。若是選擇「使用DSN」或「使用鏈接字符串」,生成器將在數據環境的BeforeOpenTables方法中生成代碼以建立所需的鏈接。若是選擇「Native」,則能夠選擇VFP數據庫容器做爲數據源;在這種狀況下,生成的代碼將確保數據庫是打開的(也可使用自由表做爲數據源)。

Cursors」頁面容許您維護DataEnvironmentCursorAdapter成員(遊標對象不會在生成器中顯示,也不能添加它們)。Add按鈕容許您向DataEnvironment添加CursorAdapter子類,而New則建立一個新的基類CursorAdapterRemove刪除Select CursorAdapterBuilder爲所選CursorAdapter調用CursorAdapter Builder。您能夠更改CursorAdapter對象的名稱,但對於任何其餘屬性,都須要CursorAdapter生成器。

 

從快捷菜單中選擇Builder也能夠調用CursorAdapter生成器。「Properties」頁顯示對象的類和名稱(只有在從DataEnvironment中調出生成器時才能更更名稱,由於它對CursorAdapter子類是隻讀的)、它將建立的遊標的別名、是否應該使用DataEnvironment的數據源以及鏈接信息(若是沒有)。與DataEnvironment生成器同樣,若是選擇「使用DSN」或「使用鏈接字符串」,CursorAdapter生成器將生成代碼以建立所需的鏈接(在本例中是CursorFill方法)。

「數據訪問」頁容許您指定SelectCmd、CursorSchema和其餘屬性。若是您指定了鏈接信息,能夠單擊SelectCmd的Build按鈕來顯示Select Command Builder,這樣就能夠輕鬆地建立SelectCmd。

Select命令生成器簡化了構建一個簡單的Select語句的工做。從「表格」下拉列表中選擇所需的表格,而後將相應的字段移到選定的一側。對於本機數據源,能夠向「表」組合框中添加表(例如,若是但願使用空閒表)。選擇OK時,SelectCmd將填充適當的SQL SELECT語句。

單擊遊標模式的「生成」按鈕,自動爲您填寫此屬性。爲了使其工做,生成器實際上建立了一個新的CursorAdapter對象,適當地設置了屬性,並調用CursorFill來建立遊標。若是您沒有到數據源的實時鏈接,或者CursorFill因爲某種緣由(例如無效的SelectCmd)失敗,那麼這顯然行不通。
使用「自動更新」頁設置VFP自動爲數據源生成更新語句所需的屬性。Tables屬性是從SelectCmd中指定的表自動填充的,fields網格是從CursorSchema中的字段填充的。與視圖設計器同樣,能夠經過檢查網格中的相應列來選擇哪些是關鍵字段,哪些字段是可更新的。還能夠設置其餘屬性,例如在將遊標發送到數據源以前轉換遊標某些字段中的數據的函數。

更新、插入和刪除頁面的外觀幾乎相同。它們容許您爲更新、刪除和插入屬性集指定值。對於VFP不能自動生成update語句的XML,這一點尤其重要。

使用本機數據

儘管很明顯CursorAdapter的目的是爲了標準化和簡化對非VFP數據的訪問,可是您能夠經過將DataSourceType設置爲「Native」來使用它來替代Cursor。你爲什麼這樣作?主要是傾向於未來應用程序升級;經過簡單地將DataSourceType更改成其餘選項之一(並可能更改其餘一些屬性,如設置鏈接信息),您能夠輕鬆地切換到其餘DBMS,如SQL Server。
當DataSourceType設置爲「Native」時,VFP將忽略DataSource。SelectCmd必須是一個SQL SELECT語句,而不是USE命令或表達式,這意味着您老是使用至關於本地視圖的語句,而不是直接使用表。您須確保VFP可找到SELECT語句中引用的任何表,所以若是這些表不在當前目錄中,則須要設置路徑或打開表所屬的數據庫。與往常同樣,若是但願遊標可更新,請確保設置更新屬性(KeyFieldList、Tables、UpdateableFieldList和UpdateNameList)。

如下示例(NativeExample.prg)從TestData VFP示例數據庫中的Customer表建立一個可更新的遊標:

local loCursor as CursorAdapter, ; 
  laErrors[1] 
open database (_samples + 'data\testdata') 
loCursor = createobject('CursorAdapter') 
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、UpdateableFieldList和UpdateNameList,VFP將自動生成適當的UPDATE、INSERT和DELETE語句,以便用任何更改更新後端。若是要改用存儲過程,請適當設置*Cmd、*CmdDataSource和*CmdDataSourceType屬性。

下面是一個示例,取自ODBCExample.prg,它調用Northwind數據庫中的CustOrderHist存儲過程,以獲取特定客戶按產品銷售的總單位:

local loCursor as CursorAdapter, ; 
  laErrors[1] 
loCursor = createobject('CursorAdapter') 
with loCursor 
  .Alias          = 'CustomerHistory' 
  .DataSourceType = 'ODBC' 
  .DataSource     = sqlstringconnect('driver=SQL Server;server=(local);' + ; 
    'database=Northwind;uid=sa;pwd=;trusted_connection=no') 
  .SelectCmd      = "exec CustOrderHist 'ALFKI'" 
  if .CursorFill() 
    browse 
  else 
    aerror(laErrors) 
    messagebox(laErrors[2]) 
  endif .CursorFill() 
endwith

使用ADO

使用ADO做爲CursorAdapter的數據訪問機制比使用ODBC有更多的問題:

  • 必須將數據源設置爲ADO記錄集,該記錄集的ActiveConnection屬性設置爲打開的ADO鏈接對象。
  • 若是要使用參數化查詢(這多是常見狀況,而不是檢索全部記錄),則必須將其ActiveConnection屬性設置爲open ADO Connection對象的ADO命令對象做爲第四個參數傳遞給CursorFill。VFP將負責爲您填充Command對象的參數集合(它解析SelectCmd以查找參數),但包含參數值的變量固然必須在做用域中。
  • 在數據環境中將一個CursorAdapter與ADO一塊兒使用很簡單:能夠將UseDEDataSource設置爲.T。若是願意,能夠像使用CursorAdapter同樣設置數據環境的DataSource和DataSourceType屬性。可是,若是數據環境中有多個CursorAdapter,則此操做不起做用。緣由是DataEnvironment.DataSource引用的ADO記錄集只能包含一個CursorAdapter的數據;當爲第二個CursorAdapter調用CursorFill時,會出現「記錄集已打開」錯誤。所以,若是您的數據環境有多個CursorAdapter,則必須將UseDEDataSource設置爲.F並本身管理每一個CursorAdapter的DataSource和DataSourceType屬性(或者可能使用爲您管理這些屬性的DataEnvironment子類)。

下面的示例代碼取自ADOExample.prg,它展現瞭如何在ADO命令對象的幫助下使用參數化查詢檢索數據。這個例子還展現了VFP 8中新的結構化錯誤處理特性的使用;對ADO鏈接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=;trusted_connection=no' 
  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

將XML與CursorAdapter結合使用須要一些額外的東西。如下是問題:

  • 數據源屬性被忽略。
  • 即便將.F.做爲第一個參數傳遞給CursorFill方法,也必須填寫CursorSchema屬性,不然將出現錯誤。
  • SelectCmd屬性必須設置爲返回遊標XML的表達式,例如用戶定義函數(UDF)或對象方法名稱。
  • 對遊標所作的更改將轉換爲diffgram,diffgram是一種XML,它包含更改字段和記錄的以前和以後的值,並在須要更新時放置在UpdateGram屬性中。
  • 爲了將更改寫回數據源,UpdateCmdDataSourceType必須設置爲「XML」,UpdateCmd必須設置爲處理更新的表達式(一樣,多是UDF或對象方法)。您可能須要將「This.UpdateGram」傳遞給UDF,以便它能夠將更改發送到數據源。

遊標的XML源能夠來自不一樣的地方。例如,能夠調用一個UDF,該UDF使用CURSORTOXML()將VFP遊標轉換爲XML,並返回結果:

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

UDF能夠調用返回結果集爲XML的Web服務。下面是一個從我在本身的系統上建立和註冊的Web服務中爲我生成的自動感應示例(細節並不重要;它只是顯示了一個Web服務的示例)。

local loWS as dataserver 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

它可使用SQLXML 3.0執行存儲在Web服務器模板文件中的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)來執行更新。

下面是一個示例(XMLExample.prg),它使用帶有XML數據源的CursorAdapter。注意,SelectCmd和UpdateCmd都調用UDF。在SelectCmd的狀況下,SQL Server 2000 XML模板的名稱和要檢索的客戶ID被傳遞給一個名爲GetNWXML的UDF,稍後咱們將討論這個UDF。對於UpdateCmd,VFP將UpdateGram屬性傳遞給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               = 'GetNWXML([customersbyid.xml?customerid=ALFKI])' 
  .Tables                  = 'CUSTOMERS' 
  .UpdatableFieldList      = 'CUSTOMERID, COMPANYNAME, CONTACTNAME, ' + ; 
    'CONTACTTITLE, ADDRESS, CITY, REGION, POSTALCODE, COUNTRY, PHONE, FAX' 
  .UpdateCmdDataSourceType = 'XML' 
  .UpdateCmd               = 'SendNWXML(This.UpdateGram)' 
  .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

此代碼引用的XML模板CustomersByID.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數據庫的虛擬目錄中(有關配置IIS以使用SQL Server的詳細信息,請參閱附錄)。
這是GetNWXML的代碼。它使用MSXML2.XMLHTTP對象訪問Web服務器上的SQL Server 2000 XML模板並返回結果。模板的名稱(以及可選的任何查詢參數)做爲參數傳遞給此代碼。

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

SendNWXML看起來很類似,只是它但願傳遞一個diffgram,將diffgram加載到MSXML2.DOMDocument對象中,並將該對象傳遞給Web服務器,而後Web服務器將經過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。您應該在瀏覽窗口中看到一條記錄(ALFKI客戶)。更改某個字段中的值,而後關閉窗口並再次運行PRG。您應該看到您的更改已寫入後端。

CursorAdapter 和 DataEnvironment 子類

與VFP中一般的狀況同樣,我建立了CursorAdapter和DataEnvironment的子類,我將使用這些子類而不是基類。

SFCursorAdapter

SFCursorAdapter(在SFDataClasses.vcx中)是CursorAdapter的一個子類,它添加了一些附加功能:

  • 它能夠自動處理參數化查詢;您能夠將參數值定義爲靜態(常量值)或動態(表達式,例如「=Thisform.txtName.value」,在打開或刷新遊標時計算)。
  • 它能夠在遊標打開後自動建立索引。
  • 它爲ADO作了一些特殊的事情,例如將數據源設置爲ADO記錄集,將記錄集的ActiveConnection屬性設置爲ADO鏈接對象,以及在使用參數化查詢時建立ADO命令對象並將其傳遞給CursorFill。
  • 它提供了一些簡單的錯誤處理(cErrorMessage屬性用錯誤消息填充)。
  • 它有CursorAdapter中缺乏的更新和發佈方法。

讓咱們來看看這個類。
Init方法建立兩個集合(使用新的集合基類,它維護事物的集合),一個用於SelectCmd屬性可能須要的參數,另外一個用於在遊標打開後應自動建立的標記。它還設置了MULTILOCKS on,由於這是CursorAdapter遊標所必需的。

with This 
* 建立參數和標記集合 
  .oParameters = createobject('Collection') 
  .oTags       = createobject('Collection') 
* 確保 MULTILOCKS 設置爲 on. 
  set multilocks on 
endwith

AddParameter方法向parameters集合添加一個參數。向此方法傳遞參數的名稱(該名稱應與SelectCmd屬性中顯示的名稱匹配)和可選的參數值(若是如今不傳遞,能夠稍後使用GetParameter方法進行設置)。這段代碼展現了VFP 8中的兩個新特性:新的Empty類(沒有PEMs),使其成爲輕量級對象的理想選擇;ADDPROPERTY()函數(其做用相似於那些沒有該方法的對象的ADDPROPERTY方法)。

lparameters tcName, ; 
  tuValue 
local loParameter 
loParameter = createobject('Empty') 
addproperty(loParameter, 'Name',  tcName) 
addproperty(loParameter, 'Value', tuValue) 
This.oParameters.Add(loParameter, tcName)

使用GetParameter方法返回一個特定的參數對象;當您想設置要用於參數的值時,一般會使用這個方法。

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

SetConnection方法用於將DataSource屬性設置爲所需的鏈接。若是DataSourceType是「ODBC」,請傳遞鏈接句柄。若是是「ADO」,則數據源須要是一個ADO記錄集,其ActiveConnection屬性設置爲打開的ADO鏈接對象,所以經過Connection對象,SetConnection將建立記錄集並將其ActiveConnection設置爲傳遞對象。

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

要建立遊標,請調用GetData方法而不是CursorFill,由於它會自動處理參數和錯誤。若是要建立遊標但不填充數據,請將.T.傳遞給GetData。此方法所作的第一件事是建立私有範圍的變量,這些變量的名稱和值與參數集合中定義的參數相同(從這裏調用的GetParameterValue方法返回參數對象的值或以「=」開頭的值的求值)。接下來,若是咱們使用ADO而且有任何參數,代碼將建立一個ADO Command對象並將其ActiveConnection設置爲Connection對象,而後將Command對象傳遞給CursorFill方法;CursorAdapter要求在參數化ADO查詢中使用該方法。若是咱們沒有使用ADO或者沒有任何參數,代碼只調用cursor fill來填充遊標。注意.T.被傳遞給CursorFill,告訴它在CursorSchema被填充時使用CursorSchema(這是我但願基類具備的行爲)。若是建立了遊標,則代碼調用CreateTags方法爲遊標建立所需的索引;若是沒有,則調用HandleError方法來處理髮生的任何錯誤。

lparameters tlNoData 
local loParameter, ; 
  lcName, ; 
  luValue, ; 
  llUseSchema, ; 
  loCommand, ; 
  llReturn 
with This 
 
*若是咱們要填充遊標(而不是建立空遊標),則建立變量來保存任何參數
*必須在這裏而不是在方法中這樣作,由於咱們但願它們的做用域是私有的

  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 
 
*嘗試填充遊標
 
    llReturn = .CursorFill(llUseSchema, tlNoData, .nOptions) 
  endif '?' $ .SelectCmd ... 
 
*若是咱們建立了遊標,請建立爲其定義的任何標記。
*若是沒有,請處理錯誤。
 
  if llReturn 
    .CreateTags() 
  else 
    .HandleError() 
  endif llReturn 
endwith 
return llReturn

Update方法很簡單:它只調用TABLEUPDATE()嘗試更新原始數據源,若是失敗則調用HandleError。

local llReturn 
llReturn = tableupdate(1, .F., This.Alias) 
if not llReturn 
  This.HandleError() 
endif not llReturn 
return llReturn

有幾種方法咱們在這裏不看,你能夠本身檢查一下。AddTag將遊標建立後要建立的索引的信息添加到tags集合,而CreateTags(從GetData調用)在INDEX ON語句中使用該集合中的信息。HandleError使用AERROR()來肯定出錯的地方,並將錯誤數組的第二個元素放入cErrorMessage屬性中。

讓咱們看幾個使用這個類的例子。第一個(取自TestCursorAdapter.prg)從Northwind數據庫的Customers表中獲取全部記錄。這段代碼與用於基類CursorAdapter的代碼沒有太大的不一樣(因爲沒有填寫CursorSchema,所以必須將.F.做爲第一個參數傳遞給CursorFill)。

loCursor = newobject('SFCursorAdapter', 'SFDataClasses') 
with loCursor 
 
*鏈接到SQL Server Northwind數據庫並獲取客戶記錄
 
  .DataSourceType = 'ODBC' 
  .DataSource     = sqlstringconnect('driver=SQL Server;server=(local);' + ; 
    'database=Northwind;uid=sa;pwd=;trusted_connection=no') 
  .Alias          = 'Customers' 
  .SelectCmd      = 'select * from customers' 
  if .GetData() 
    browse 
  else 
    messagebox('Could not get the data. The error message was:' + ; 
      chr(13) + chr(13) + .cErrorMessage) 
  endif .GetData() 
endwith

下一個示例(也取自TestCursorAdapter.prg)使用SFConnectionMgr的ODBC版原本管理鏈接,咱們在「VFP中的數據策略:基礎篇」一文中查看了該版本。它還爲SelectCmd使用參數化語句,顯示AddParameter方法如何容許您處理參數,並演示如何使用AddTag方法自動爲遊標建立標記。

loConnMgr = newobject('SFConnectionMgrODBC', 'SFRemote') 
with loConnMgr 
  .cDriver   = 'SQL Server' 
  .cServer   = '(local)' 
  .cDatabase = 'Northwind' 
  .cUserName = 'sa' 
  .cPassword = '' 
endwith 
if loConnMgr.Connect() 
  loCursor = newobject('SFCursorAdapter', 'SFDataClasses') 
  with loCursor 
    .DataSourceType = 'ODBC' 
    .SetConnection(loConnMgr.GetConnection()) 
    .Alias     = 'Customers' 
    .SelectCmd = 'select * from customers where country = ?pcountry' 
    .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() 
  endwith 
else 
  messagebox(loConnMgr.cErrorMessage) 
endif loConnMgr.Connect()

SFDataEnvironment

SFDataEnvironment(也在SFDataClasses.vcx中)比SFCursorAdapter簡單得多,但添加了一些有用的功能:

  • GetData方法調用全部SFCursorAdapter成員的GetData方法,所以沒必要單獨調用它們。
  • 相似地,Requery和Update方法調用每一個SFCursorAdapter成員的Requery和Update方法。
  • 與SFCursorAdapter相似,SetConnection方法將數據源設置爲ADO記錄集,並將記錄集的ActiveConnection屬性設置爲ADO鏈接對象。可是,它也調用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稍微複雜一點:它調用任何具備該方法且UseDEDataSource設置爲.F.的成員對象的SetConnection方法,而後使用相似於SFCursorAdapter中的代碼設置本身的數據源(若是任何CursorAdapter的UseDEDataSource設置爲.T.)。

lparameters tuConnection 
local llSetOurs, ; 
  loCursor, ; 
  llReturn 
with This 
 
*調用任何不使用數據源的CursorAdapter的SetConnection方法
 
  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 
 
*若是發現使用數據源的CursorAdapter,須要設置數據源
 
  if llSetOurs 
    do case 
      case .DataSourceType = 'ODBC' 
        .DataSource = tuConnection 
      case .DataSourceType = 'ADO' 
        .DataSource = createobject('ADODB.RecordSet') 
        .DataSource.ActiveConnection = tuConnection 
    endcase 
  endif llSetOurs 
endwith

Requery和Update幾乎與GetData相同,因此咱們沒必要費心去查看它們。

TestDE.prg顯示瞭如何使用SFDataEnvironment做爲兩個SFCursorAdapter類的容器。因爲此示例使用ADO,所以每一個SFCursorAdapter都須要本身的數據源,故UseDEDataSource設置爲.F。請注意,對DataEnvironment SetConnection方法的單個調用負責爲每一個CursorAdapter設置數據源屬性。

loConnMgr = newobject('SFConnectionMgrADO', 'SFRemote') 
with loConnMgr 
  .cDriver   = 'SQLOLEDB.1' 
  .cServer   = '(local)' 
  .cDatabase = 'Northwind' 
  .cUserName = 'sa' 
  .cPassword = '' 
endwith 
if loConnMgr.Connect() 
  loDE = newobject('SFDataEnvironment', 'SFDataClasses') 
  with loDE 
    .NewObject('CustomersCursor', 'SFCursorAdapter', 'SFDataClasses') 
    with .CustomersCursor 
      .Alias          = 'Customers' 
      .SelectCmd      = 'select * from customers' 
      .DataSourceType = 'ADO' 
    endwith 
    .NewObject('OrdersCursor', 'SFCursorAdapter', 'SFDataClasses') 
    with .OrdersCursor 
      .Alias          = 'Orders' 
      .SelectCmd      = 'select * from orders' 
      .DataSourceType = 'ADO' 
    endwith 
    .SetConnection(loConnMgr.GetConnection()) 
    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 
else 
  messagebox(loConnMgr.cErrorMessage) 
endif loConnMgr.Connect()

可重用數據類

如今咱們有了CursorAdapter和DataEnvironment子類,讓咱們討論一下可重用的數據類。
VFP開發人員要求微軟在VFP中添加的一件事是可重用的數據環境。例如,您可能有一個表單和一個報表具備徹底相同的數據設置,可是您必須手動爲每一個表單和報表填充數據環境,由於數據環境是不可重用的。一些開發人員(以及幾乎全部的框架供應商)經過在代碼中建立數據環境(它們不能可視化地被子類化)並在表單上使用「loader」對象來實例化數據環境子類,使得建立可重用的數據環境變得更加容易。然而,這是一種混亂,並無幫助報告。
如今,在VFP 8中,咱們可以建立兩個可重用的數據類,它們能夠提供從任何數據源到任何須要它們的數據源的遊標,以及可重用的數據環境,後者能夠託管數據類。在撰寫本文時,您不能在報表中使用CursorAdapter或DataEnvironment子類,但能夠經過編程添加CursorAdapter子類(例如在DataEnvironment的Init方法中)來利用那裏的可重用性。

咱們來爲Northwind客戶和訂單表建立數據類。首先,建立SFCursorAdapter的一個子類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生成器完成大部分工做,特別是設置CursorSchema和更新屬性。訣竅是打開「use connection settings in builder only」(僅在生成器中使用鏈接設置)選項,填寫鏈接信息以創建實時鏈接,而後填寫SelectCmd並使用生成器爲您構建其他屬性。

如今,只要您須要Northwind Customers表中的記錄,就只需使用CustomersCursor類。固然,咱們尚未定義任何鏈接信息,但這其實是件好事,由於這個類沒必要擔憂如何獲取數據(ODBC、ADO或XML),甚至沒必要擔憂要使用什麼數據庫引擎(用於SQL Server、Access和新版VFP8的Northwind數據庫)。
可是請注意,這個遊標涉及Customers表中的全部記錄。有時候,你只想要一個特定的客戶。因此,讓咱們建立一個CustomersCursor的子類CustomerByIDCursor。將SelectCmd更改成「select * from customers where customerid = ?pcustomerid」並將如下代碼放入Init:

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

這將建立一個名爲pCustomerID的參數(與SelectCmd中指定的名稱相同),並將其設置爲傳遞的任意值。若是未傳遞任意值,請使用GetParameter返回此參數的對象,並在調用GetData以前設置其Value屬性。

建立一個相似於CustomersCursor的orderscorsor類,只是它從Orders表中檢索全部記錄。而後建立一個OrdersForCustomerCursor子類,該子類只檢索特定客戶的訂單。將SelectCmd設置爲「select * from orders where customerid = ?pcustomerid」,並將與CustomerByIDCursor相同的代碼放入Init(由於它是相同的參數)。

要測試其效果,請運行TestCustomersCursor.prg。

示例:Form

如今咱們有了一些可重用的數據類,來用一下它們。首先,讓咱們建立一個名爲CustomersAndOrdersDataEnvironment的SFDataEnvironment子類,它包含CustomerByIDCursor和OrdersForCustomerCursor類。將AutoOpenTables設置爲.F(由於咱們須要在打開表以前設置鏈接信息),並將CursorAdapter和UseDEDataSource設置爲.T。如今能夠以某種形式使用此數據環境來顯示有關特定客戶的信息,包括其訂單。

讓咱們建立這樣一個表單。建立一個名爲CustomerOrders.scx的表單(它包含在本文檔附帶的示例文件中),將DEClass和DEClassLibrary設置爲CustomersAndOrdersDataEnvironment,以便咱們使用可重用的數據環境。將如下代碼放入Load方法中:

#define ccDATASOURCETYPE 'ADO' 
with This.CustomersAndOrdersDataEnvironment 
 
*設置數據環境數據源
  .DataSourceType = ccDATASOURCETYPE 
 
*若是咱們使用ODBC或ADO,請建立一個鏈接管理器
*並打開鏈接到Northwind數據庫的鏈接
  if .DataSourceType $ 'ADO,ODBC' 
    This.AddProperty('oConnMgr') 
    This.oConnMgr = newobject('SFConnectionMgr' + ccDATASOURCETYPE, ; 
      'SFRemote') 
    with This.oConnMgr 
      .cDriver   = iif(ccDATASOURCETYPE = 'ADO', 'SQLOLEDB.1', ; 
        'SQL Server') 
      .cServer   = '(local)' 
      .cDatabase = 'Northwind' 
      .cUserName = 'sa' 
      .cPassword = '' 
    endwith 
    if not This.oConnMgr.Connect() 
      messagebox(oConnMgr.cErrorMessage) 
      return .F. 
    endif not This.oConnMgr.Connect() 
 
*若是咱們使用ADO,每一個遊標都必須有本身的數據源
    if .DataSourceType = 'ADO' 
      .CustomerByIDCursor.UseDEDataSource      = .F. 
      .CustomerByIDCursor.DataSourceType       = 'ADO' 
      .OrdersForCustomerCursor.UseDEDataSource = .F. 
      .OrdersForCustomerCursor.DataSourceType  = 'ADO' 
    endif .DataSourceType = 'ADO' 
 
*將數據源設置爲鏈接
    .SetConnection(This.oConnMgr.GetConnection()) 
 
*若是使用的是XML,請更改SelectCmd以調用GetNWXML函數
  else 
    .CustomerByIDCursor.SelectCmd = 'GetNWXML([customersbyid.xml?' + ; 
      'customerid=] + pCustomerID)' 
    .CustomerByIDCursor.UpdateCmdDataSourceType = 'XML' 
    .CustomerByIDCursor.UpdateCmd = 'SendNWXML(This.UpdateGram)' 
    .OrdersForCustomerCursor.SelectCmd = 'GetNWXML([ordersforcustomer.' + ; 
      'xml?customerid=] + pCustomerID)' 
    .OrdersForCustomerCursor.UpdateCmdDataSourceType = 'XML' 
    .OrdersForCustomerCursor.UpdateCmd = 'SendNWXML(This.UpdateGram)' 
  endif .DataSourceType $ 'ADO,ODBC' 
 
*指定將從CustomerID文本框中填充遊標參數的值
  loParameter       = .CustomerByIDCursor.GetParameter('pCustomerID') 
  loParameter.Value = '=Thisform.txtCustomerID.Value' 
  loParameter       = .OrdersForCustomerCursor.GetParameter('pCustomerID') 
  loParameter.Value = '=Thisform.txtCustomerID.Value' 
 
*建立空遊標並在失敗時顯示錯誤消息
  if not .GetData(.T.) 
    messagebox(.cErrorMessage) 
    return .F. 
  endif not .GetData(.T.) 
endwith

這看起來像不少代碼,但其中大部分是爲了演示目的,以容許切換到不一樣的數據訪問機制。

此代碼建立一個鏈接管理器來處理鏈接(ADO、ODBC或XML),具體取決於ccDATASOURCETYPE常量,您能夠更改該常量以嘗試每一個機制。對於ADO,因爲每一個CursorAdapter都必須有本身的數據源,所以爲每一個CursorAdapter設置UseDEDataSource和DataSourceType屬性。而後,代碼調用SetConnection方法來設置鏈接信息。對於XML,SelectCmd、UpdateCmdDataSourceType和UpdateCmd屬性必須如前所述進行更改。接下來,代碼使用兩個CursorAdapter對象的GetParameter方法將pCustomerID參數的值設置爲表單中文本框的內容。注意在值中使用「=」;這意味着每次須要時都會對Value屬性求值,所以咱們基本上有一個動態參數(當用戶在文本框中鍵入時,保存將參數不斷更改成當前值的須要)。最後,調用GetData方法來建立空遊標,以便控件的數據綁定能夠工做。

在表單上放置一個文本框並將其命名爲txtCustomer,將如下代碼放入其Valid方法中:

with Thisform 
  .CustomersAndOrdersDataEnvironment.Requery() 
  .Refresh() 
endwith

這將致使在輸入客戶ID時從新查詢遊標和刷新控件。

在表單上放置一個標籤,放在文本框旁邊,並將其標題設置爲「客戶ID」。
將CompanyName、ContactName、Address、City、Region、PostalCode和Country字段從DataEnvironment中的Customers遊標拖動到表單中,以建立這些字段的控件。而後在Orders遊標中選擇OrderID、EmployeeID、OrderDate、RequiredDate、ShippedDate、ShipVia和Freight字段,並將它們拖到表單中以建立網格(Grid--譯者注)。
就這樣子。運行表單並輸入「ALFKI」做爲客戶ID。當您在文本框中選擇選項卡時,您應該會看到客戶地址信息和訂單。嘗試更改有關客戶或訂單的內容,而後關閉表單,再次運行它,而後再次輸入「ALFKI」。您應該看到,您所作的更改已寫入後端數據庫,而無需您付出任何努力。

很酷吧?這比基於本地表或視圖建立表單要簡單得多。更好的方法是,嘗試將ccDATASOURCETYPE常量更改成「ADO」或「XML」,並注意表單的外觀和工做方式徹底相同。這就是CursorAdapters的要點!

示例:Report

咱們試一個Report。此處討論的示例取自此文檔附帶的CustomerOrders.frx。這裏最大的問題是,與表單不一樣,咱們不能告訴報表使用DataEnvironment子類,也不能在DataEnvironment中刪除CursorAdapter子類。所以,咱們必須在報表中放入一些代碼,以便將CursorAdapter子類添加到數據環境中。儘管將此代碼放入報表數據環境的BeforeOpenTables事件中彷佛是合乎邏輯的,但實際上這不會起做用,由於我不明白爲何,在預覽報表時,BeforeOpenTables會在每一個頁面上激發。因此,咱們將把代碼放入Init方法中。

#define ccDATASOURCETYPE 'ODBC' 
with This 
  set safety off 
 
*設置數據環境數據源
  .DataSourceType = ccDATASOURCETYPE 
 
*爲客戶和訂單建立CursorAdapter對象
  .NewObject('CustomersCursor', 'CustomersCursor', 'NorthwindDataClasses') 
  .CustomersCursor.AddTag('CustomerID', 'CustomerID') 
  .NewObject('OrdersCursor', 'OrdersCursor', 'NorthwindDataClasses') 
  .OrdersCursor.AddTag('CustomerID', 'CustomerID') 
 
*若使用ODBC或ADO,請建立一個鏈接管理器
*並打開鏈接到Northwind數據庫的鏈接
  if .DataSourceType $ 'ADO,ODBC' 
    .AddProperty('oConnMgr') 
    .oConnMgr = newobject('SFConnectionMgr' + ccDATASOURCETYPE, ; 
      'SFRemote') 
    with .oConnMgr 
      .cDriver   = iif(ccDATASOURCETYPE = 'ADO', 'SQLOLEDB.1', ; 
        'SQL Server') 
      .cServer   = '(local)' 
      .cDatabase = 'Northwind' 
      .cUserName = 'sa' 
      .cPassword = '' 
    endwith 
    if not .oConnMgr.Connect() 
      messagebox(.oConnMgr.cErrorMessage) 
      return .F. 
    endif not .oConnMgr.Connect() 
 
*若是使用ADO,每一個遊標都必須有本身的數據源
    if .DataSourceType = 'ADO' 
      .CustomersCursor.UseDEDataSource = .F. 
      .CustomersCursor.DataSourceType  = 'ADO' 
      .CustomersCursor.SetConnection(.oConnMgr.GetConnection()) 
      .OrdersCursor.UseDEDataSource = .F. 
      .OrdersCursor.DataSourceType  = 'ADO' 
      .OrdersCursor.SetConnection(.oConnMgr.GetConnection()) 
    else 
      .CustomersCursor.UseDEDataSource = .T. 
      .OrdersCursor.UseDEDataSource    = .T. 
      .DataSource = .oConnMgr.GetConnection() 
    endif .DataSourceType = 'ADO' 
    .CustomersCursor.SetConnection(.oConnMgr.GetConnection()) 
    .OrdersCursor.SetConnection(.oConnMgr.GetConnection()) 
 
*若使用XML,請更改SelectCmd以調用GetNWXML函數
  else 
    .CustomersCursor.SelectCmd      = 'GetNWXML([getallcustomers.xml])' 
    .CustomersCursor.DataSourceType = 'XML' 
    .OrdersCursor.SelectCmd         = 'GetNWXML([getallorders.xml])' 
    .OrdersCursor.DataSourceType    = 'XML' 
  endif .DataSourceType $ 'ADO,ODBC' 
 
*獲取數據並在失敗時顯示錯誤消息
  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() 
 
*設置從客戶到訂單的關係
  set relation to CustomerID into Customers 
endwith

此代碼看起來與窗體的代碼相似。一樣,大多數代碼是處理不一樣的數據訪問機制。可是,還有一些額外的代碼,由於咱們不能使用DataEnvironment子類,必須本身編寫行爲代碼。

如今,咱們如何方便地把字段放在Report上?因爲CursorAdapter在設計時不存在於數據環境中,所以咱們不能將字段從它們拖到Report中。這裏有一個提示:建立一個PRG來建立遊標並將其留在做用域中(經過掛起或使CursorAdapter對象公開),而後使用Quick Report函數將具備適當大小的字段放在Report上。
在CUSTOMERS.CUSTOMERID上建立一個組並選中「在新頁面上啓動每一個組」。而後將Report佈局爲相似於如下內容:


XMLAdapter

除了CursorAdapter以外,VFP 8還有三個新的基類來改進VFP對XML的支持:XMLAdapter、XMLTable和XMLField。XMLAdapter提供了一種在XML和VFP遊標之間轉換數據的方法。它的功能比CURSORTOXML()和XMLTOCURSOR()函數多得多,包括支持分層XML和使用那些函數不支持的XML類型(如ADO.NET數據集)的功能。XMLTable和XMLField是子對象,它們提供微調XML數據的模式的能力。此外,XMLTable還有一個ApplyDiffgram方法,它容許VFP使用updategrams和diffgrams,這是VFP 7中缺乏的。

爲了讓您瞭解它的功能,我建立了一個返回ADO.NET數據集的ASP.NET Web服務,而後使用VFP中的XMLAdapter對象來使用該數據集。如今我作到了。

首先,在Visual Studio.NET中,我將Northwind Customers表從服務器資源管理器拖到一個名爲NWWebService的新ASP.NET Web服務項目中。這會自動建立兩個對象,SQLConnection1和SQLDataAdapter1。而後,我將如下代碼添加到現有生成的代碼中:

<WebMethod()> Public Function GetAllCustomers() As DataSet 
    Dim loDataSet As New DataSet() 
    Me.SqlConnection1.Open() 
    Me.SqlDataAdapter1.Fill(loDataSet) 
    Return loDataSet 
End Function

我構建該項目是爲了在NWWebService虛擬目錄(VS.NET自動爲我建立)中生成適當的Web服務文件。

爲了在VFP中使用這個Web服務,我使用IntelliSense管理器註冊了一個名爲「Northwind.NET」的Web服務,指向「http://localhost/NWWebService/NWWebService.asmx?WSDL」做爲WSDL文件的位置。而後我建立了如下代碼(在XMLAdapterWebService.prg中)來調用Web服務並將ADO.NET數據集轉換爲VFP遊標。

local loWS as Northwind.NET, ; 
  loXMLAdapter as XMLAdapter, ; 
  loTable as XMLTable 
 
*從.NET Web服務獲取.NET數據集
loWS = NEWOBJECT("Wsclient",HOME()+"ffc\_webservices.vcx") 
loWS.cWSName = "Northwind.NET" 
loWS = loWS.SetupClient("http://localhost/NWWebService/NWWebService.asmx" + ; 
  "?WSDL", "NWWebService", "NWWebServiceSoap") 
loXML = loWS.GetAllCustomers() 
 
*建立一個XMLAdapter並加載數據
loXMLAdapter = createobject('XMLAdapter') 
loXMLAdapter.XMLSchemaLocation = '1' 
loXMLAdapter.LoadXML(loXML.Item(0).parentnode.xml) 
 
*若是成功地加載了XML,那麼從每一個表對象建立並瀏覽一個遊標
if loXMLAdapter.IsLoaded 
  for each loTable in loXMLAdapter.Tables 
    loTable.ToCursor() 
    browse 
    use 
  next loTable 
endif loXMLAdapter.IsLoaded

注意,爲了使用XMLAdapter,您須要在系統上安裝MSXML 4.0服務包1或更高版本。您能夠從MSDN網站下載(http://MSDN.microsoft.com並搜索MSXML)。

總結

我認爲CursorAdapter是VFP 8中最大和最使人興奮的加強之一,由於它提供了一個一致且易於使用的遠程數據接口,並且它容許咱們建立可重用的數據類。我相信一旦你用它來工做,你會發現他們和我同樣使人興奮。

做者介紹:

Doug Hennig是Stonefield Systems Group Inc.的合做夥伴。他是獲獎的Stonefield數據庫工具包(SDT)的做者和獲獎的Stonefield查詢的共同做者。他是《黑客視覺FoxPro 7.0指南》的合著者(與Tamar Granor、Ted Roche和Della Martin一塊兒)和《視覺FoxPro 7.0的新特性》的合著者(與Tamar Granor和Kevin McNeish一塊兒),均來自Hentzenwerke出版社,在Pinnacle Publishing的Pros Talk VisualFoxPro系列中,「VisualFoxPro數據字典」的做者。他在FoxTalk上寫了每個月的「可重用工具」專欄。他是《黑客指南》和《基礎知識》的技術編輯,這兩本書都來自亨森沃克出版社。自1997年以來,道格在每次微軟FoxPro開發者大會(DevCon)以及北美各地的用戶團體和開發者大會上都發表過演講。他是微軟最有價值的專業人士(MVP)和認證專業人士(MCP)。

附錄:設置SQL Server 2000 XML訪問存取

另文,本文略……

相關文章
相關標籤/搜索