VFP的數據策略:高級篇


VFP的數據策略:高級篇

作者:Doug Hennig  翻譯:老瓷

引語

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

CursorAdapter

在我看來,CursorAdapter是VFP 8中最大的新特性之一。我覺得他們這么酷的原因是:

  • 使得使用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訪問存取

另文,本文略……


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM