电竞比分网-中国电竞赛事及体育赛事平台

分享

DataSnap基礎(chǔ)

 aaie_ 2014-01-15
1. DATASNAP 歷史

作為MIDAS起始于Delphi3,Delphi4是MIDAS II,Delphi5中是MIDASIII,而后基于COM遠(yuǎn)程數(shù)據(jù)模塊方式使
用TCP/IP,HTTP,(D)COM構(gòu)建出強(qiáng)大的通訊能力.從Delphi6開始改名為DataSnap,直到D2007這個(gè)框架一直在使
用.D2009重新架構(gòu)了DataSnap—移除COM依賴,使用TCP/IP以更輕量級(jí)的方式生成遠(yuǎn)程服務(wù)對(duì)象和客戶端連接
能力.同時(shí)提供了與Delphi Prism2009開發(fā)的.NET程序通訊的功能.

Delphi2010中構(gòu)建于D2009架構(gòu)之上,并對(duì)此架構(gòu)做了進(jìn)一步的擴(kuò)展,包括使用兩個(gè)向?qū)韯?chuàng)建新的
部署目標(biāo)(VCL窗體,Window服務(wù),控制臺(tái)及面向Web的ISAPI,CGI或Web App Debugger).HTTP(S)傳輸協(xié)
議,HTTP驗(yàn)證,客戶端回調(diào)函數(shù),REST和JSON的支持,及使用過濾器來支持壓縮和解壓縮.

1.1 DATASNAP范例數(shù)據(jù)位置

本白頁中我建議您使用Demo和范例來學(xué)習(xí).雖然Delphi支持很多數(shù)據(jù)庫系統(tǒng),使用DBX4,ADO dbGo,或
其他數(shù)據(jù)存取技術(shù),為了演示方便我這里使用DBX4來操作BlackfishSQL的employee.jds數(shù)據(jù)庫.見
[ C:\Documents and Settings\All Users\Documents\RAD
Studio\7.0\Demos\database\databases\BlackfishSQL\employee.jds].在截圖中可以看到我使用的
是Windows Vista或Win7操作系統(tǒng),使用Windows Server 2008 Web編輯器來部署DataSnap ISAPI服務(wù).

2. DATASNAP目標(biāo):如何獲取數(shù)據(jù)

DataSnap2010支持三種不同的Windows方式:VCL窗體,Windows服務(wù)和控制臺(tái)應(yīng)用程序.本節(jié)中我們
將討論他們的好處,不同和每種方式最適合在什么情況下使用.

下面會(huì)創(chuàng)建一個(gè)DataSnap服務(wù)端和客戶端,我們將講解
TDSServer,TDSServerClass,TDSTCPServerTransport,TDSHTTPService,TDSHTTPWebDispatcher和
TDSHTTPServiceAuthenticationManager組件,以及自定義的服務(wù)方法和TDSServerModule類.

將討論不同的傳輸協(xié)議(TCP,HTTP)的好處及傳輸效率.并討論DataSnap服務(wù)對(duì)象的不同生命期選項(xiàng)
(Server,Session,Invocation),及他們的效率和使用的建議.最后,討論部署.

2.1. DATASNAP 服務(wù)端范例

在Object Repository中有兩個(gè)不同的DataSnap服務(wù)向?qū)?一個(gè)是生成基于Windows的Datasnap服務(wù)
項(xiàng)目,一個(gè)是生成基于WebBroker的DataSnap服務(wù)項(xiàng)目(需要部署到IIS或Apache).我們將會(huì)演示.

啟動(dòng)了Delphi2010,點(diǎn)擊File.New.Other,你會(huì)在Object Repository中看到DataSnap服務(wù)向?qū)е?BR>顯示的三個(gè)圖標(biāo):DataSnap Server,DataSnap WebBroker Server,和Server Module.

雙擊第一個(gè)(后面的兩個(gè)在下面的小結(jié)中講解),彈出如下對(duì)話框:

界面中第一部分是控制項(xiàng)目類型的.默認(rèn)可以生成可視化的帶有主窗體的VCL窗體應(yīng)用程序.第二個(gè)
選項(xiàng)是創(chuàng)建控制臺(tái)應(yīng)用程序,生成一個(gè)控制臺(tái)窗口—可以用來輸出請(qǐng)求應(yīng)答信息(用Writeln語句輸出
服務(wù)應(yīng)用程序正在做什么).這兩種方式都是為了做范例或最初部署,很少用于最終部署.由于
DataSnap架構(gòu)不再基于COM,客戶端將不能使服務(wù)端啟動(dòng).因此為了響應(yīng)客戶端的請(qǐng)求,DataSnap服務(wù)
端應(yīng)該一直在運(yùn)行.如果你希望應(yīng)用7X24小時(shí)全天候運(yùn)行,DataSnap服務(wù)端必須同時(shí)也在運(yùn)行中.對(duì)應(yīng)
VCL窗體或控制臺(tái)應(yīng)用程序,需要一個(gè)賬戶登錄到Windows中后才能啟動(dòng)DataSnap服務(wù),背離了這種要


求.第三者選擇在這時(shí)最適合:一個(gè)Windows服務(wù)應(yīng)用程序,安裝后配置成為自動(dòng)啟動(dòng),當(dāng)計(jì)算機(jī)啟動(dòng)后
將自動(dòng)運(yùn)行(不需要賬戶登錄).服務(wù)應(yīng)用程序不會(huì)彈出界面,很難調(diào)試Bug.然而,為了整合這三種的優(yōu)
勢(shì),我將用幾分鐘創(chuàng)建一個(gè)項(xiàng)目組,包括VCL窗體應(yīng)用程序的DataSnap服務(wù),控制臺(tái)DataSnap服務(wù),及
Windows服務(wù)Datasnap服務(wù),都共享同一個(gè)自定義的服務(wù)方法,這樣就可以開發(fā)一個(gè)Datasnap服務(wù)應(yīng)用
程序,在需要的時(shí)候編譯出三個(gè)不同類型的部署方式.

第二部分是選擇使用的Datasnap服務(wù)的通訊協(xié)議.和DanaSnap2009相比,我們可以看到多了一個(gè)
HTTP通訊,及HTTP驗(yàn)證.為了更加靈活,這里建議選擇全部選項(xiàng),我們可以同時(shí)使用TCP/IP,HTTP,及使
用HTTP引入的HTTP驗(yàn)證.

第三部分已經(jīng)為我們配置好了,如果我們要提供一個(gè)服務(wù)方法類,我們可以選擇它的基
類:TPersistent,TDataModule或TDSServerModule.推薦使用最后的一個(gè)選項(xiàng),可使用RTTI來啟動(dòng)執(zhí)行
函數(shù) (也可能你覺得使用TDataModule更合適—不操作數(shù)據(jù)庫,或不使用其他非可視控件,這時(shí)使用
TPersitent也夠用了).

現(xiàn)在是從DSServer.pas中貼出來的一小段代碼,來說明TDSServerModule和
TProviderDataModule(也是繼承于TDataModule)之間的關(guān)系.

TDSServerModuleBase = class(TProviderDataModule)

public

procedure BeforeDestruction; override;

destructor Destroy; override;

end;

{$MethodInfo ON}

TDSServerModule = class(TDSServerModuleBase)

end;

{$MethodInfo OFF}

當(dāng)無法確定時(shí)就使用TDSServerModule選項(xiàng)作為基類.

2.1.1. 創(chuàng)建多目標(biāo)項(xiàng)目組-- VCL 窗體項(xiàng)目

如上面所說,這里創(chuàng)建多目標(biāo)的Datasnap服務(wù)項(xiàng)目組.首先創(chuàng)建一個(gè)VCL窗體應(yīng)用程序作為Datasnap
服務(wù),選擇所有的通訊協(xié)議.

   默認(rèn)創(chuàng)建了一個(gè)叫做Project1.dproj的項(xiàng)目,并帶有三個(gè)單元文
件,ServerContainerUnit1.pas,ServerMethodUnit1.pas和Unit1.pas.首先File.Save Project As保
存項(xiàng)目,并輸入有實(shí)際意義的文件名稱.將Unit1.pas保存為MainForm.pas,ServerMethodsUnit1.pas
保存為ServerMethodsUnitDemo.pas文件,保存Project1.dproj為DataSnapServer.dproj.

稍后我們將向項(xiàng)目組添加控制臺(tái)應(yīng)用程序和Window服務(wù)應(yīng)用程序.首先我們來檢查一下項(xiàng)目,并編
譯工程.如果你編譯DataSnapServer項(xiàng)目,將會(huì)出現(xiàn)一個(gè)錯(cuò)誤信息(由于我們將
ServerMethodsUnit1.pas改名所致).錯(cuò)誤原因是由于ServerContainerUnitDemo.pas單元中的
Implementation部分引用了ServerMethodsUnit1.pas單元.為了修復(fù)這個(gè)沖突,修改引用單元的文件
名稱,從新編譯.這是發(fā)現(xiàn)在第37行出現(xiàn)錯(cuò)誤,使用了ServerMethodsUnit1中的TServerMethods1類型.
修改ServerMethodsUnit1為ServerMethodsUnitDemo.這時(shí)可以正確的編譯項(xiàng)目了.

ServerContainerUnitDemo的引用部分應(yīng)該向下面代碼所示:

implementation

uses

Windows, ServerMethodsUnitDemo;

{$R *.dfm}

procedure TServerContainer1.DSServerClass1GetClass(

DSServerClass: TDSServerClass; var PersistentClass: TPersistentClass);

begin

PersistentClass := ServerMethodsUnitDemo.TServerMethods1;

end;

end.

2.1.1.1. SERVERCONTAINERUNITDEMO

打開ServerContainerUnitDemo單元,將會(huì)看到不少于五個(gè)組件:一個(gè)TDSServer,一個(gè)
TDSServerClass,一個(gè)TDSTCPServerTransport(用于TCP/IP通訊),一個(gè)TDSHTTPService(用于HTTP
通訊),一個(gè)TDSHTTPServiceAuthenticationManager組件(用于HTTP驗(yàn)證).

前面兩個(gè)一直會(huì)存在,其他的三個(gè)則是根據(jù)選擇的通訊協(xié)議生成的.

2.1.1.1.1. TDSSERVER

TDSServer組件只有四個(gè)屬性,AutoStart,HideDSAdmin,Name和Tag.AutoStart屬性默認(rèn)設(shè)置為
True,意味著在窗體創(chuàng)建后自動(dòng)啟動(dòng)DataSnap服務(wù).如果將AutoStart設(shè)置為False,需要手動(dòng)調(diào)用
Start方法啟動(dòng)服務(wù),并調(diào)用Stop方法停止服務(wù).可以調(diào)用Started方法驗(yàn)證DataSnap服務(wù)是否已經(jīng)
啟動(dòng).


HideAdmin屬性默認(rèn)設(shè)置為False.如果設(shè)置為True,連接到DataSnap服務(wù)的客戶端將無法調(diào)用
Datasnap服務(wù)中的TDSAdmin類的內(nèi)置方法.TDSAdmin不是一個(gè)真正的類,我們可以調(diào)用的TDSAdmin
方法定義在DSNames單元:

TDSAdminMethods = class

public

const CreateServerClasses = 'DSAdmin.CreateServerClasses';

const CreateServerMethods = 'DSAdmin.CreateServerMethods';

const FindClasses = 'DSAdmin.FindClasses';

const FindMethods = 'DSAdmin.FindMethods';

const FindPackages = 'DSAdmin.FindPackages';

const GetPlatformName = 'DSAdmin.GetPlatformName';

const GetServerClasses = 'DSAdmin.GetServerClasses';

const GetServerMethods = 'DSAdmin.GetServerMethods';

const GetServerMethodParameters = 'DSAdmin.GetServerMethodParameters';

const DropServerClasses = 'DSAdmin.DropServerClasses';

const DropServerMethods = 'DSAdmin.DropServerMethods';

const GetDatabaseConnectionProperties = 'DSAdmin.GetDatabaseConnectionProperties';

end;

TDSServer組件有五個(gè)事件:OnConnect,OnDisconnect,OnError,OnPrepare和OnTrace.我們可以
實(shí)現(xiàn)這五個(gè)事件來響應(yīng)不同的情況,例如向日志文件中寫入日志.

OnConnect,OnDisconnect,OnError和OnPrepare事件有一個(gè)繼承于TDSEventObject的參數(shù),包含
了DxContext,傳輸,服務(wù)和DbxConnection組件的屬性,在OnConnect和OnDisconnect事件中
TDSConnectEventObject類型還包含了ConnectionProperties和ChannelInfo屬性.
TDSConnectEventObject也包括了由錯(cuò)誤引起的異常, TDSConnectEventObject還包括了我們要使
用的MethodAlias和ServerClass屬性.

OnTrace事件有一個(gè)TDBXTraceInfo類型的參數(shù).注意由于這個(gè)OnTrace事件處理程序也會(huì)包含一
些代碼錯(cuò)誤,如TDBXTraceInfo和CBRType是編譯器未知的.為了解決這個(gè)問題,我們需要引用
DBXCommon單元(為識(shí)別TDBXTraceInfo類型)和DBComonTypes單元(為識(shí)別CBRType類型).

在OnConnect事件處理中,我們可以通過ChannelInfo來查看連接信息,例如(使用自定義的函數(shù)
LogInfo向日志文件中寫入信息):

procedure TServerContainer1.DSServer1Connect(

DSConnectEventObject: TDSConnectEventObject);

begin

LogInfo('Connect ' + DSConnectEventObject.ChannelInfo.Info);

end;

在OnTrace事件處理程序中我們可以使用TraceInfo.Message中的信息記錄服務(wù)端正在做什么.

function TServerContainer1.DSServer1Trace(TraceInfo: TDBXTraceInfo): CBRType;

begin

LogInfo('Trace ' + TraceInfo.CustomCategory);

LogInfo(' ' + TraceInfo.Message);

Result := cbrUSEDEF; // take default action

end;

注意,在客戶端也可以使用連接到TSQLConnection組件的TSQLMonitor組件來跟蹤DataSnap服務(wù)
端和客戶端之間的通訊(在創(chuàng)建這個(gè)DataSnap服務(wù)的客戶端時(shí)講解).

一個(gè)跟蹤日志輸出如下所示:

17:05:55.492 Trace

17:05:55.496 read 136 bytes:{"method":"reader_close","params":[1,0]}


{"method":"prepare","params":[-1,false,"DataSnap.ServerMethod",

"TServerMethods1.AS_GetRecords"]}

17:05:55.499 Prepare

如你所見,TraceInfo.Message中包括了傳輸信息的字節(jié)數(shù)和被調(diào)用的方法名稱等信息.

2.1.1.1.2. TDSSERVERCLASS

TDSServerClass組件將服務(wù)端特定的類發(fā)布給遠(yuǎn)程客戶端(使用動(dòng)態(tài)方法調(diào)用).

TDSServerClass組件有一個(gè)Server屬性指向TDSServer組件.其他除了Name和Tag外的重要屬
性是LifeCycle.默認(rèn)是Session,但是也可設(shè)置為Server或Invocation.從長到短,Server,Session
和Invocation的意思是一個(gè)類的實(shí)例在服務(wù)端的生命周期為整個(gè)服務(wù),一個(gè)DataSnap會(huì)話或一次
方法調(diào)用.Session表示每個(gè)連接將獲取其自己的服務(wù)類實(shí)例.如果將其改為Invocation,將會(huì)得到
一個(gè)無狀態(tài)的服務(wù)類—可用于部署CGI Web服務(wù)應(yīng)用程序(其也是無狀態(tài)的,每個(gè)請(qǐng)求都進(jìn)行加載
卸載).將LifeCycle改為Server,則所有的連接請(qǐng)求使用一個(gè)服務(wù)類實(shí)例.這可以用于計(jì)算請(qǐng)求數(shù)
量,但是必須自己保證線程安全.

TDSServerClass有四個(gè)事件:OnCreateInstance,OnDestroyInstance(當(dāng)實(shí)例創(chuàng)建和注銷時(shí)觸
發(fā)).OnGetClass和OnPrepare.OnPrepare事件可用于準(zhǔn)備服務(wù)方法.使用D2009或使用D2010手動(dòng)向
容器中添加TDSServerClass時(shí),OnGetClass事件必須由我們自己實(shí)現(xiàn),以便于指定一個(gè)可遠(yuǎn)程調(diào)用
的類.在D2010的向?qū)е?已經(jīng)自動(dòng)為我們實(shí)現(xiàn)了OnGetClass事件,如下:

procedure TServerContainer1.DSServerClass1GetClass(

DSServerClass: TDSServerClass; var PersistentClass: TPersistentClass);

begin

PersistentClass := ServerMethodsUnitDemo.TServerMethods1;

end;

注意:當(dāng)我們重命名了自動(dòng)生成的代碼單元ServerMethodsUnit1為
ServerContainerUnitDemo.pas后必須修改這里.

2.1.1.1.3. TDSTCPSERVERTRANSPORT

TDSTCPServerTransport組件負(fù)責(zé)在DataSnap服務(wù)端和客戶端進(jìn)行通訊,使用TCP/IP協(xié)議.

TDSTCPServerTransport組件有五個(gè)重要的屬性:BufferKBSize,Filters(D2010新特
性),MaxThreads,PoolSize,Port和Server.

BufferKBSize屬性指定通訊緩沖區(qū)大小,默認(rèn)設(shè)置為32KB.Filters屬性可以包含一個(gè)傳輸過濾
器集合,將在第四節(jié)講解.MaxThreads屬性定義最大線程數(shù)(默認(rèn)為0不限制).PoolSize可用于連接
池(如果修改了這里,也需要相應(yīng)的修改DataSnap客戶端).

Server屬性指向TDSServer組件.TDSTCPServerTransport組件沒有事件.

2.1.1.1.4. TDSHTTPSERVICE

TDSHTTPService組件負(fù)責(zé)使用HTTP協(xié)議組織DataSnap服務(wù)端和客戶端通訊.

TDSHTTPService組件有十個(gè)屬性(除了Name和
Tag):Active,AuthenticationManager,DSHostName,DSPort,Filters,HttpPort,Name,RESTContext,Server,和只讀的ServerSoftware屬性.

Active屬性指定DSHTTPService開始偵聽請(qǐng)求.可以在設(shè)計(jì)時(shí)設(shè)置,但是這會(huì)影響DataSnap服務(wù)
在運(yùn)行時(shí)啟動(dòng)(由于DSHTTPService組件偵聽了同一個(gè)端口—在設(shè)計(jì)時(shí)啟動(dòng)偵聽,在運(yùn)行時(shí)就不能
再啟動(dòng)一個(gè)偵聽了).最好的方式在在TServerContainer的OnCreate事件中激活TDSHTTPService:

procedure TServerContainer1.DataModuleCreate(Sender: TObject);

begin

DSHTTPService1.Active := True;

end;

AuthenticationManager屬性用于定義處理HTTP驗(yàn)證的管理組件,這里指向了
TDSHTTPServiceAuthenticationManager組件.這個(gè)組件在下節(jié)詳述.

DSHostName和DSPort屬性用于定義DataSnap服務(wù)端連接,但只有在沒有指定Server屬性時(shí)生效.


通常都是使用Server屬性.

Filters屬性可以包含一系列傳輸過濾器,在第四節(jié)詳述.

HttpPort屬性定義DSHTTPService組件偵聽的特定端口以響應(yīng)連接.注意這個(gè)屬性默認(rèn)是80端口,
通常在發(fā)布時(shí)必須做修改(IIS等Web服務(wù)已占用了80端口).

RESTContext屬性指定REST上下文URL,這樣就可以以REST服務(wù)的方式調(diào)用DataSnap服務(wù).默
認(rèn),RESTContext屬性設(shè)置為rest,我們可以用http://localhost/datasnap/rest/...來調(diào)用服務(wù).
在第六節(jié)詳述REST,JSON和客戶端回調(diào)函數(shù).

最后,Server屬性指向同一個(gè)容器中的TDSServer組件.如果沒有指定Server屬性,也可以使用
DSHostName和DSPort屬性連接到使用TCP的DataSnap服務(wù).當(dāng)設(shè)置了Server屬性,DSHostName和
DSport屬性失效.

TDSHTTPService組件有五個(gè)事件:四個(gè)是REST相關(guān)的,一個(gè)是跟蹤事件,REST相關(guān)事件將在第六
節(jié)詳述.

OnTrace事件可用于跟蹤對(duì)DSHTTPService組件的調(diào)用,例如:

procedure TServerContainer1.DSHTTPService1Trace(Sender: TObject;

AContext: TDSHTTPContext; ARequest: TDSHTTPRequest;

AResponse: TDSHTTPResponse);

begin

LogInfo('HTTP Trace ' + AContext.ToString);

LogInfo(' ' + ARequest.Document);

LogInfo(' ' + AResponse.ResponseText);

end;

注意HTTP跟蹤信息只有當(dāng)客戶端使用HTTP連接到服務(wù)端是才會(huì)觸發(fā)(默認(rèn)使用TCP/IP協(xié)議).

跟蹤輸出如下所示:

17:05:55.398 HTTP Trace TDSHTTPContextIndy

17:05:55.400 /datasnap/tunnel

17:05:55.403 OK

從中可見,AContext設(shè)置為TDSHTTPContextIndy,ARequest設(shè)置為/DataSnap/tunnel,AResponse
設(shè)置為OK.

2.1.1.1.5. TDSHTTPSERVICEAUTHENTICATIONMANAGER

當(dāng)選中HTTP通訊協(xié)議的驗(yàn)證復(fù)選框后,TDSHTTPServiceAuthenticationManager組件將自動(dòng)出現(xiàn)
在服務(wù)容器中.也可手動(dòng)添加到服務(wù)容器中,當(dāng)然TDSHTTPService組件的AuthenticationManager
屬性必須指向TDSHTTPServiceAuthenticationManager組件.

TDSHTTPServiceAuthenticationManager組件有一個(gè)事件:OnHTTPAuthenticate事件,可以驗(yàn)證
Datasnap客戶端到服務(wù)端連接的HTTP信息.

procedure TServerContainer1.DSHTTPServiceAuthenticationManager1HTTPAuthenticate(

Sender: TObject; const Protocol, Context, User, Password: string;

var valid: Boolean);

begin

if (User = 'Bob') and (Password = 'Swart') then

valid := True

else

valid := False

end;

當(dāng)然,你可以使用數(shù)據(jù)庫技術(shù)來擴(kuò)展驗(yàn)證方式.客戶端最好使用HTTPS方式將用戶名和密碼等
HTTP驗(yàn)證信息發(fā)送到服務(wù)端,所以我希望易博龍可以在現(xiàn)有HTTP和TCP/IP基礎(chǔ)上在添加一個(gè)HTTPS
協(xié)議.

HTTPS可以確保連接安全和數(shù)據(jù)包加密, 數(shù)據(jù)包被竊取也不會(huì)泄露用戶和密碼信息.可與你所在


域的ISP或Web管理員協(xié)商是否有可能使用HTTPS—--強(qiáng)烈推薦(如我使用的
https://www.bobswart.hl/).

DataSnap服務(wù)應(yīng)用程序另一個(gè)優(yōu)勢(shì)是可將HTTP驗(yàn)證信息(所用協(xié)議和上下文信息)記錄下來.以
便于查找誰登陸過,誰試圖登錄(如做欺騙登錄操作試圖獲取DataSnap服務(wù)的權(quán)限).

2.1.1.2. SERVERMETHODSUNITDEMO

注意我們已經(jīng)檢查了ServerContainerUnitDemo.pas單元,現(xiàn)在看一下DataSnap服務(wù)的另外重要
的單元:ServerMethodsUnitDemo.pas單元.在新建DataSnap服務(wù)對(duì)話框中我們指定了
TDSServerModule類作為基類,因此TServerMethods1類型繼承于TDSServerModule(其繼承于
TDSServerModuleBase,又繼承于TProvideDataModule,添加一個(gè)析構(gòu)函數(shù)和BeforeDestruction過
程. TProvideDataModule繼承于正常的TDataModule,增加了服務(wù)提供者的能力 更多信息見后
面).

由于TServerMethods1繼承于TDataModule,設(shè)計(jì)時(shí)可以看到一個(gè)數(shù)據(jù)模塊的可視區(qū)域.我們可以
向其中添加一下不可視組件,如數(shù)據(jù)存取控件等.在第三節(jié)將操作數(shù)據(jù)庫,現(xiàn)在保持設(shè)計(jì)區(qū)為空,僅
向TServerMethods1類添加方法.

如果不想向項(xiàng)目組添加其他類型的項(xiàng)目—DataSnap控制臺(tái)應(yīng)用程序及DataSnap Windows服務(wù)應(yīng)
用程序,可跳到2.1.4節(jié)查看實(shí)現(xiàn)服務(wù)方法.

2.2. DATASNAP 客戶端

第一個(gè)DataSnap服務(wù)端范例啟動(dòng)并偵聽請(qǐng)求,現(xiàn)在創(chuàng)建一個(gè)客戶端.本節(jié)中,我們將講解如何在
客戶端連接服務(wù)端,如何導(dǎo)入方法生產(chǎn)服務(wù)類.

確信DataSnap服務(wù)已啟動(dòng),我們創(chuàng)建一個(gè)DataSnap客戶端應(yīng)用程序項(xiàng)目.為了在設(shè)計(jì)時(shí)方便切換
DataSnap服務(wù)項(xiàng)目和DataSnap客戶端項(xiàng)目,將客戶端項(xiàng)目添加到同一個(gè)項(xiàng)目組.任何類型的項(xiàng)目都
可作為DataSnap客戶端項(xiàng)目,這里我們選擇VCL窗口應(yīng)用程序,保存為DataSnapClient.dpr,主窗體
為ClientForm.pas.控件欄中DataSnap服務(wù)目錄中含有六個(gè)DataSnap(服務(wù))組件,DataSnap客戶端
目錄中不含我們現(xiàn)在需要的組件.

DataSnap客戶端目錄中的組件都是一些老的DataSnap組件,可以使用,但不推薦.但可使用新
的TDSProviderConnect組件,可以在新的DataSnap客戶端上連接老的DataSnap服務(wù)(3.2中詳述).

除了DataSnap客戶端目錄,我們還要看看dbEpress目錄,可以找到一個(gè)新的組件叫做
TSQLServerMethod(注意:在下一個(gè)截圖中這個(gè)新組件很容易發(fā)現(xiàn),其用TSql前綴替代了TSQL前
綴).

TSqlServerMethod組件可用于調(diào)用DataSnap服務(wù)的遠(yuǎn)程方法,但首先需要連接到DataSnap服務(wù).

可以使用TSQLConnect組件建立連接—--不在使用原來的TXXXConnection組件.為了靈活
性,TSQLConnection組件的Drive屬性下拉框中有個(gè)新選項(xiàng):DataSnap.在ClientForm上放置一個(gè)
TSQLConnection組件,設(shè)置其Driver屬性為DataSnap.Driver屬性將變成可展開的對(duì)象,展開后如
下圖:

注意:默認(rèn)CommunicationProtocol屬性為空,TSQLConnection將使用TCP/IP作為通信協(xié)議(端口
211).BufferKBSize為32(KB),端口設(shè)置為211(與服務(wù)端設(shè)置相同).實(shí)際應(yīng)用中一般將端口號(hào)設(shè)置
為其他端口(同時(shí)修改服務(wù)端客戶端),因?yàn)?11端口是DataSnap默認(rèn)端口不安全.

HostName,UserName和Password用于連接到DataSnap服務(wù).在本地測試時(shí),將HostName設(shè)置為
localhost,通??梢栽O(shè)置為服務(wù)器名稱,DNS或IP地址.

不要忘記將TSQLConnection的LoginPropt屬性設(shè)置為False,否則將會(huì)在連接的時(shí)候彈出登錄窗


口.

一旦TSQLConnection的Driver屬性設(shè)置好,就可以設(shè)置Connected屬性為True去連接DataSnap服
務(wù)端.注意這時(shí)DataSnap服務(wù)必須運(yùn)行才能連接成功.

2.2.1. DATASNAP客戶端類

在確定連接正常后,右擊TSQLConnection組件,選擇Generate DataSnap Class選項(xiàng),將生成一個(gè)
新單元,默認(rèn)叫做Unit1,含有一個(gè)叫做TServerMethods1Client的類(在DataSnap服務(wù)方法類名稱
后加了一個(gè)Client).保存單元為ServerMethodsClient.pas.

這個(gè)TServerMethods1Client類如下所示

type

TServerMethods1Client = class

private

FDBXConnection: TDBXConnection;

FInstanceOwner: Boolean;

FEchoStringCommand: TDBXCommand;

FServerTimeCommand: TDBXCommand;

public

constructor Create(ADBXConnection: TDBXConnection); overload;

constructor Create(ADBXConnection: TDBXConnection;

AInstanceOwner: Boolean); overload;

destructor Destroy; override;

function EchoString(Value: string): string;

function ServerTime: TDateTime;

end;

如你所見,TServerMethods1Client類有兩個(gè)構(gòu)造方法,一個(gè)析構(gòu)方法和兩個(gè)我們?cè)诜?wù)端定義
的服務(wù)方法.

為使用這些方法,將ServerMethodsClient單元引用到ClientForm,在客戶端窗體上放置一個(gè)按
鈕,在按鈕的OnClick事件中寫如下代碼:

procedure TForm2.Button1Click(Sender: TObject);

var

Server: TServerMethods1Client;

begin

Server := TServerMethods1Client.Create(SQLConnection1.DBXConnection);

try

ShowMessage(DateTimeToStr(Server.ServerTime))

finally

Server.Free

end;

end;

這個(gè)代碼將創(chuàng)建一個(gè)TServerMethods1Client類實(shí)例,然后調(diào)用ServerTime服務(wù)類,最后釋放這
個(gè)DataSnap服務(wù)的代理對(duì)象.

點(diǎn)擊按鈕將彈出對(duì)話框顯示服務(wù)端的時(shí)間.


同樣方法測試EchoString.

2.2.1.1. HTTP COMMUNICATION PROTOCOL

注意我已經(jīng)提到TSQLConnection組件以TCP/IP作為默認(rèn)通訊協(xié)議.這樣我們就看不到任何HTTP
跟蹤信息(在2.1.1.1.4小結(jié)中定義).然而,很容易修改通訊協(xié)議,僅需在TSQLConnection的Driver
屬性中的CommunicationProtocal子屬性中輸入HTTP即可.注意這是我們還要修改Port屬性,因?yàn)?BR>211被TCP/IP占用,同時(shí)要修改服務(wù)端的TDSHTTPService組件的端口號(hào).

修改后運(yùn)行DataSnap客戶端,會(huì)出現(xiàn)如下錯(cuò)誤:

解決方法是在DataSnap客戶端向ClientForm中添加DSHTTPLayer單元引用.

2.2.1.2. HTTP 驗(yàn)證

使用HTTP通訊協(xié)議的好處之一是可以使用其包含的HTTP驗(yàn)證.由DataSnap服務(wù)端的
TDSHTTPServiceAuthenticationManager組件所支持(詳見2.1.1.1.5).

如果實(shí)現(xiàn)了OnHTTPAuthenticate事件處理,將會(huì)核對(duì)HTTP驗(yàn)證.我們必須保證輸入正確的信息才
能確保TDSHTTPServiceAuthenticationManager驗(yàn)證通過.否則將會(huì)得到一個(gè)HTTP/1.1 401未驗(yàn)證
錯(cuò)誤.

HTTP檢驗(yàn)從客戶端傳遞到服務(wù)端的用戶名和密碼,及其他TDSHTTPServiceAuthentication特定
信息,我們需要在DataSnap客戶端的TSQLConnection控件中填寫DSAuthUser和DSAuthPassword屬
性.

注意我們也需要指定HostName的值,除非是在同一臺(tái)電腦上測試.

2.3. DATASNAP服務(wù)部署

范例的服務(wù)端和客戶端在同一臺(tái)電腦上運(yùn)行良好,但是實(shí)際環(huán)境中,DataSnap服務(wù)將運(yùn)行在服務(wù)
器上,一或多臺(tái)客戶端通過網(wǎng)絡(luò)連接服務(wù)端.服務(wù)端程序通常部署在沒有安裝Delphi的電腦上.這
種情況下就需要考慮不用運(yùn)行時(shí)包來編譯DataSnap,而僅生成一個(gè)大的可執(zhí)行文件.由于我們還沒
有使用任何數(shù)據(jù)操作組件,也不需要任何的其他數(shù)據(jù)庫驅(qū)動(dòng)或DLL文件.

2.3.1. DATASNAP 客戶端部署

假設(shè)客戶端與服務(wù)端運(yùn)行在不同的電腦上,我們必須保證客戶端可以連接到服務(wù)端.為了保證這
點(diǎn),客戶端的TSQLConnection組件不能僅僅指定Driver屬性中的CommunicationProtocol和端口,
還需要指定HostName屬性.設(shè)置IP地址或DNS.例如連接我的DataSnap服務(wù)的HostName屬性
http://www./.(注意不需要指定http://前綴,因?yàn)樵贑ommunicationProtocol屬性中指定了
通訊協(xié)議).

3. DATASNAP和數(shù)據(jù)庫

除了使用Delphi2010 DataSnap框架創(chuàng)建簡單的服務(wù)方法,我們還可以在服務(wù)端添加數(shù)據(jù)庫操作,
實(shí)現(xiàn)多層數(shù)據(jù)庫應(yīng)用—DataSnap服務(wù)連接到數(shù)據(jù)庫,但DataSnap客戶端是瘦客戶端,不含任何數(shù)據(jù)
庫驅(qū)動(dòng).

我們同樣可以快速創(chuàng)建數(shù)據(jù)庫操作的DataSnap范例,只使用SQLConnection組件和已創(chuàng)建的客戶
端類.也可使用另外兩個(gè)DataSnap組件TsqlServerMethod和TDSProviderConnection.

首先,我們要確保服務(wù)端公布了一個(gè)數(shù)據(jù)集,打開ServerMethodsUnitDemo并在數(shù)據(jù)模塊中添加
一個(gè)TSQLConnection組件.連接TSQLConnection組件到數(shù)據(jù)庫和表(本例我連接到BlackFishSQL的


Employees表).將TSQLConnection組件添加到數(shù)據(jù)模塊的設(shè)計(jì)區(qū)域.設(shè)置Driver屬性為
BlachFishSql.然后設(shè)置Database屬性為employee.jds文件的路徑: C:\Documents and Settings\All

Users\Documents\RAD Studio\7.0\Demos\database\databases\BlackfishSQL on Windows

XP, or C:\Users\Public\Documents\ RAD Studio\7.0\Demos\database\databases\BlackfishSQL.
將TSQLConnection的LoginPrompt屬性設(shè)置為False.設(shè)置Connected屬性為True驗(yàn)證是否能正確連
接.

下一步,添加一個(gè)TSQLDataSet組件,將其SQLConnection設(shè)置為TSQLConnection組件.

設(shè)置其CommandType為ctQuery,雙擊CommandText屬性輸入SQL語句.

SELECT EMP_NO, FIRST_NAME, LAST_NAME, HIRE_DATE, JOB_COUNTRY FROM EMPLOYEE

確保在設(shè)計(jì)時(shí)設(shè)置了TSQLConnection組件的LoginPrompt和Connected屬性為False,及
TSQLDataSet的Active屬性為False.

現(xiàn)在在ServerMethodsUnitDemo單元的TServerMethods1類中添加一個(gè)public方法返回一個(gè)
TSQLDataSet組件.

type


TServerMethods1 = class(TDSServerModule)

SQLConnection1: TSQLConnection;

SQLDataSet1: TSQLDataSet;

private

{ Private declarations }

public

{ Public declarations }

function EchoString(Value: string): string;

function ServerTime: TDateTime;

function GetEmployees: TDataSet;

end;

如你所見,TServerMethods1類中定義了一個(gè)叫做GetEmployees的方法,實(shí)現(xiàn)如下:

function TServerMethods1.GetEmployees: TDataSet;

begin

SQLDataSet1.Open; // make sure data can be retrieved

Result := SQLDataSet1

end;

重新編譯服務(wù)并運(yùn)行.注意如你關(guān)閉了服務(wù)但無法重新編譯,可能是DataSnap服務(wù)還在運(yùn)行.

3.1. TSQLSERVERMETHOD

回到DataSnap客戶端,TSQLConnection組件應(yīng)該已經(jīng)斷開到DataSnap服務(wù)端的連接(如果仍然連
接,說明你沒有重新編譯服務(wù)端).重新將Connected設(shè)置為True,從服務(wù)端更新信息,并重新從服務(wù)
端生成客戶端類(使用右鍵菜單).

為了重寫原來的ServerMethodsClient單元,推薦從項(xiàng)目中移除原來的ServerMethodsClient后
在選擇’Generate DataSnap Client Classes’方法.保存了新的單元文件后不需要修改客戶端代
碼.生成了新文件后,可以從中看到GetEmplorees方法.

type

TServerMethods1Client = class

private

FDBXConnection: TDBXConnection;

FInstanceOwner: Boolean;

FEchoStringCommand: TDBXCommand;

FServerTimeCommand: TDBXCommand;

FGetEmployeesCommand: TDBXCommand;

public

constructor Create(ADBXConnection: TDBXConnection); overload;

constructor Create(ADBXConnection: TDBXConnection;

AInstanceOwner: Boolean); overload;

destructor Destroy; override;

function EchoString(Value: string): string;

function ServerTime: TDateTime;

function GetEmployees: TDataSet;

end;

為使用GetEmployees方法獲取TDataSet,我們可以使用TsqlServerMethod組件.將
TsqlServerMethod組件添加到客戶端窗體,設(shè)置其SQLConnection屬性為SQLConnection1,然后打
開ServerMethodName下拉框顯示可用的方法:一些DSAdmin方法(可通過設(shè)置TDSServer的
HideDSAdmin屬性為True禁止),接下來是三個(gè)DSMetaData方法,七個(gè)TServerMethods.As_XXX方法
(由原來的IAppServer提供),和最后我們自己的TServerMethods1的方法:EchoString,ServerTime,


和GetEmployees方法.

本例我們選擇TServerMethods1.GetEmployees作為ServerMethodName屬性的值.根據(jù)
ServerMethodName屬性值,SqlServerMethod控件獲取查詢結(jié)果記錄集.

我們使用TDataSetProvider,TClientDataSet和TDataSource來使數(shù)據(jù)顯示在TDBGrid中.

在客戶端添加一個(gè)TDataSetProvider控件,將其DataSet設(shè)置為SqlServerMethod控件.下一步添
加一個(gè)TClientDataSet控件,將其ProviderName設(shè)置為DataSetProvider控件.設(shè)置其
RemoteServer屬性為空—--這是用于原有DataSnap的方法,在新的DataSnap架構(gòu)中不再使用.

最后,放一個(gè)TDataSource控件,設(shè)置其DataSet為TClientDataSet控件,然后就可以放TGBGrid和
TDBNavigator控件了.設(shè)置其DataSource為TDataScource組件,將可以在客戶端窗體中看到數(shù)據(jù).

為了在設(shè)計(jì)時(shí)驗(yàn)證連接,我們?cè)O(shè)置ClientDataSet的Active屬性為True.將觸發(fā)SqlServerMethod
的Active為True(開始檢索數(shù)據(jù),然后自動(dòng)將Active設(shè)置為False),同時(shí)會(huì)將
TSQLConnection.Connected設(shè)置為True連接到服務(wù)端.

這種方式連接一直保持,TCliendtDataSet和TSQLConnection的被激活,數(shù)據(jù)顯示在TDBGrid中.

這種方式很容易以只讀方式查看數(shù)據(jù).注意這里說只讀,因?yàn)門SQLServerMethod不允許
TDataSetProvide-TClientDataSet組合將客戶端的數(shù)據(jù)變更傳回服務(wù)端.

TSQLServerMethod是輕量級(jí)方式連接獲取只讀數(shù)據(jù).這樣,如果你需要獲取DataSnap服務(wù)器上的
數(shù)據(jù)但不需要做修改,當(dāng)前這種發(fā)布一個(gè)返回TSQLDataSet結(jié)果集方法的架構(gòu)就很好.

然而,有時(shí)我們需要更新數(shù)據(jù),這時(shí)就必須使用不同的方法獲取數(shù)據(jù).

3.2. TDSPROVIDERCONNECTION

如果我們想提交更新,我們就需要TDSProviderConnection組件與服務(wù)端的TDataSetProvider關(guān)
聯(lián),因此我們不但可以讀取數(shù)據(jù)也可以修改數(shù)據(jù).

首先,我們需要修改服務(wù)端數(shù)據(jù)模塊,現(xiàn)有的只是返回一個(gè)TDataSet,我們必須添加一個(gè)實(shí)際的
TDataSetProvider,確保將其從DataSnap服務(wù)端發(fā)布到客戶端.所以,回到ServerMethodsUnitDemo
單元放一個(gè)TDataSetProvider組件,設(shè)置其DataSet屬性為TSQLDataSet.應(yīng)該將TDataSetProvider
重命名,如dspEmplyees;

現(xiàn)在,重新編譯DataSnap服務(wù),運(yùn)行.然后修改客戶端.

3.2.1. TDSPROVIDERCONNECTION 客戶端

為檢索到發(fā)布的TDataSetProvider組件需要修改一下DataSnap客戶端.將TSQLServerMethod和
TDataSetProvider組件從客戶端窗體刪除,添加一個(gè)TDSProviderConnection組件.設(shè)置其
SQLConnection屬性為TSQLConnection組件,連接到DataSnap服務(wù).我們也需要設(shè)置一個(gè)
ServerClassname屬性值(很不幸不能選擇只能輸入).現(xiàn)在只能手動(dòng)輸入TDSServerModule的名字,
這里是TServerMethods1.

在前一個(gè)例子中,TClientDataSet只設(shè)置了一個(gè)ProviderName屬性.然而,使用
TDSProviderConnection組件,我們必須首先設(shè)置其RemoteServer屬性為TDSproviderConnection
組件,然后設(shè)置ProviderName屬性(這個(gè)屬性還為原來設(shè)置的DataSetProvider1,現(xiàn)在設(shè)置為
dsEmployeer----在服務(wù)端發(fā)布的TDataSetProvider組件的名稱).

在ProvideName屬性的下拉框中顯示dspEmployees選項(xiàng)(在服務(wù)端的ServerDataModule單元中發(fā)
布的名稱).

現(xiàn)在我們可以設(shè)置TClientDataSet.Active為True,在設(shè)計(jì)時(shí)查看數(shù)據(jù).

設(shè)置TClientDataSet.Active為True,同時(shí)將TSQLConnection.Connected置為True.注意在設(shè)計(jì)
時(shí)最好不要將這兩個(gè)屬性設(shè)置為True.首先,如果你在IDE中打開DataSnap客戶端項(xiàng)目,將會(huì)試圖連
接服務(wù)端,如果服務(wù)端沒開啟將失敗.其次,如果在運(yùn)行時(shí)啟動(dòng)應(yīng)用程序,并且連接不可用,應(yīng)用程
序?qū)?huì)拋出異常.從而應(yīng)用程序不能使用本地?cái)?shù)據(jù),導(dǎo)致遠(yuǎn)程連接無效將不能使用應(yīng)用程序.


最好的方式是使用菜單選項(xiàng)或按鈕明確的設(shè)置TSQLConnection組件連接,TClientDataSet組件
進(jìn)行獲取數(shù)據(jù).并提交包括username/password的信息,將在后面說明.現(xiàn)在確保
TClientDataSet.Active屬性設(shè)置為False,同時(shí)TSQLConnection.Connected屬性為False.在客戶
端窗體中放一個(gè)按鈕,在OnClick事件中明確的打開TClientDataSet.

procedure TForm2.Button2Click(Sender: TObject);

begin

ClientDataSet1.Open;

end;

現(xiàn)在添加代碼提交數(shù)據(jù)變更,保存回服務(wù)器.

3.2.2 數(shù)據(jù)庫更新

有兩種方法將數(shù)據(jù)修改保存會(huì)服務(wù)端:自動(dòng)和手動(dòng).都是調(diào)用一下方法,但是會(huì)自動(dòng)調(diào)用或手動(dòng)
調(diào)用,各有優(yōu)缺點(diǎn).

對(duì)于自動(dòng)方式,我們可以使用TClientDataSet的數(shù)據(jù)修改時(shí)觸發(fā)的OnAfterInsert,OnAfterPost
和OnAfterDelete事件.在事件處理程序中,實(shí)現(xiàn)很簡單,調(diào)用TClientDataSet的ApplyUpdates方法,
發(fā)送變更,將Delta包發(fā)送到服務(wù)端保存回?cái)?shù)據(jù)庫.

procedure TForm2.ClientDataSet1AfterPost(DataSet: TDataSet);

begin

ClientDataSet1.ApplyUpdates(0);

end;

如果發(fā)生了更新錯(cuò)誤,將會(huì)觸發(fā)TClientDataSet的OnReconcileError事件,更多信息見3.2.3

手動(dòng)方式發(fā)生更新也是使用TClientDataSet的ApplyUpdates方法.但是這時(shí)方法不在
OnAfterInsert,OnAfterPost和OnAfterDelete事件中執(zhí)行.而是我們添加一個(gè)按鈕讓用戶顯示的提交更
新.

procedure TForm2.btnUpdateClick(Sender: TObject);

begin

ClientDataSet1.ApplyUpdates(0);

end;

自動(dòng)提交的好處當(dāng)然用戶不會(huì)忘記將變更保存回服務(wù)端.然而,缺點(diǎn)是無法提供Undo能力.一旦
提交數(shù)據(jù)就更新回了服務(wù)器.另外,如果使用手動(dòng)提交,所有變更保存在客戶端---TClientDataSet
組件的內(nèi)存中.這樣就允許用戶Undo部分變更:使特定記錄或全部記錄放棄更新.點(diǎn)擊更新按鈕顯
示調(diào)用ApplyUpdate方法.可能會(huì)導(dǎo)致用戶忘記提交修改數(shù)據(jù).我們應(yīng)該在窗口關(guān)閉時(shí)添加代碼檢
查TClientDataSet中是否還有未提交數(shù)據(jù)(檢查TClientDataSet.ChangeCount屬性).

3.2.3. RECONCILE ERRORS

TClientDataSet.ApplyUpdates方法有一個(gè)參數(shù):應(yīng)用更新時(shí)允許發(fā)生的最大錯(cuò)誤數(shù)量.如果有
兩個(gè)客戶端連接到了DataSnap服務(wù)端,獲取Employees數(shù)據(jù)并同時(shí)修改了第一行數(shù)據(jù).依據(jù)目前為
止我們的實(shí)現(xiàn),兩個(gè)客戶端都會(huì)使用TClientDataSet的ApplyUpdates將數(shù)據(jù)變更到DataSnap服務(wù)
端.如果都將ApplyUpdates的參數(shù)MaxErrors設(shè)置為0,則第二個(gè)客戶端的提交將會(huì)停止.第二個(gè)客
戶端應(yīng)該使用一個(gè)大于0的參數(shù)指定允許的錯(cuò)誤/沖突數(shù).然而,即使第二個(gè)客戶端將MaxErrors設(shè)
置為-1(不管有多少錯(cuò)誤發(fā)生都繼續(xù)提交后面的更新記錄),都不會(huì)提交被第一個(gè)用戶更新過的記
錄.換句話說,你需要執(zhí)行一系列沖突處理來解決這些更新已經(jīng)被更新的記錄或列的沖突問題.

幸運(yùn)的是,Delphi提供了一個(gè)很強(qiáng)大的對(duì)話框來處理這個(gè)問題.當(dāng)在DataSnap客戶端需要做一些
沖突處理時(shí),都可以使用這個(gè)對(duì)話框(或自己實(shí)現(xiàn),但最終都是處理沖突問題).

使用Delphi提供的功能,File.New.Other,在Delphi文件子目錄中選擇Reconcile Error對(duì)話框
圖標(biāo).

選中這個(gè)圖標(biāo)點(diǎn)擊OK,保存為RecError.pas,加入到DataSnapClient項(xiàng)目.這個(gè)單元包括了定義
和實(shí)現(xiàn)更新錯(cuò)誤對(duì)話框.來解決數(shù)據(jù)庫更新錯(cuò)誤.

ReconcileErrorForm窗體實(shí)例將按需要?jiǎng)討B(tài)創(chuàng)建.那么如何使用這個(gè)特殊的ReconcileErrorForm窗體呢?
好,其實(shí)很簡單.對(duì)于每個(gè)沒有成功更新的記錄,都會(huì)觸發(fā)TClientDataSet的OnReconcileError事件.定義如
下:

procedure TForm2.ClientDataSet1ReconcileError(DataSet: TClientDataSet;

E: EReconcileError; UpdateKind: TUpdateKind;


var Action: TReconcileAction);

這個(gè)事件處理程序與四個(gè)參數(shù),第一個(gè)是拋出錯(cuò)誤的TClientDataSet,第二個(gè)參數(shù)是引發(fā)錯(cuò)誤沖
突的原因,第三個(gè)參數(shù)是更新類型UpdateKind(insert,delete,modify),第四個(gè)參數(shù)是你要如何處
理沖突.可以返回如下枚舉類型值:

- raSkip:不更新這條記錄,但在變更日志中保留未提交的變更,下次提交在試.

-raAbort:取消記錄沖突處理..

-raMerge:將更新記錄與遠(yuǎn)程數(shù)據(jù)庫記錄合并,僅在客戶端變更修改過的遠(yuǎn)程字段

-raCorrect:使用正確的值替換更新記錄,這需要用戶介入.

-raCancel:對(duì)本記錄的修改全部放棄.回到初始值狀態(tài).

-raRefresh:對(duì)本記錄修改全部放棄,但重新加載當(dāng)前數(shù)據(jù)庫的記錄值.

關(guān)于ReconcileErrorForm不需要考慮全部執(zhí)行選項(xiàng).只需要做兩件事件.一,在DataSnap客戶端主窗體中
引用錯(cuò)誤處理對(duì)話框單元.二,在OnReconcileError事件中寫一行代碼調(diào)用ReconcileErrorForm單元中的
HandleReconcileError全局函數(shù). HandleReconcileError函數(shù)也有四個(gè)同樣的參數(shù),只需要按順序傳遞即可.
如下所示:

procedure TFrmClient.ClientDataSet1ReconcileError(DataSet: TClientDataSet;

E: EReconcileError; UpdateKind: TUpdateKind;

var Action: TReconcileAction);

begin

Action := HandleReconcileError(DataSet, UpdateKind, E)

end;

3.2.4. 示范沖突錯(cuò)誤

現(xiàn)在最大的問題是:實(shí)際工作中如何使用的沖突處理?為了測試,需要兩個(gè)或更多DataSnap客戶
端同時(shí)運(yùn)行.為使用當(dāng)前的客戶端和服務(wù)端進(jìn)行測試,需要執(zhí)行如下步驟:

-啟動(dòng)服務(wù)端應(yīng)用程序

-啟動(dòng)第一個(gè)客戶端應(yīng)用程序,點(diǎn)擊鏈接按鈕,獲取數(shù)據(jù)

-啟動(dòng)第二個(gè)客戶端應(yīng)用程序,點(diǎn)擊鏈接按鈕,獲取數(shù)據(jù)

-使用第一個(gè)客戶端應(yīng)用程序,修改第一行數(shù)據(jù)的FirstName列

-使用第二個(gè)客戶端應(yīng)用程序,修改第一行數(shù)據(jù)的FirstName列

-在第一個(gè)客戶端應(yīng)用程序中點(diǎn)擊更新按鈕

-在第二個(gè)客戶端應(yīng)用程序中點(diǎn)擊更新按鈕,這時(shí)將會(huì)發(fā)生一個(gè)或多個(gè)錯(cuò)誤.因?yàn)榈谝粋€(gè)應(yīng)用程
序已經(jīng)修改了同一行的同一個(gè)列.引起沖突. OnReconcileError被觸發(fā).

-進(jìn)入更新錯(cuò)誤對(duì)話框,現(xiàn)在可以處理沖突(忽略(Abort),取消(Abort),合并(Merge),更正(Correct),取消
(Cancel),更新(Refresh)).測試一下Skip和Cancel的不同,及Correct,Refresh和Merge的不同.

Skip移動(dòng)到下一行記錄,忽略更新請(qǐng)求.但其更新將保留在更新日志中.Cancel也忽略更新請(qǐng)求
同時(shí)清除本記錄所有以前的更新記錄.

Refresh清除本記錄所有的變更記錄,并將數(shù)據(jù)庫中的值作為當(dāng)前記錄的值.Merge試圖將數(shù)據(jù)庫
記錄和更新記錄合并.將變更提交到數(shù)據(jù)庫.更新和合并的記錄都不會(huì)再進(jìn)行處理,記錄已經(jīng)與數(shù)
據(jù)庫同步.

Correct是一個(gè)強(qiáng)大的選項(xiàng),在事件處理中給你一個(gè)指定更新記錄值的機(jī)會(huì).需要寫代碼或彈出
對(duì)話框指定新值.

3.3. DATASNAP 數(shù)據(jù)庫部署

部署一個(gè)使用數(shù)據(jù)庫的DataSnap服務(wù)需要比部署一個(gè)簡單DataSnap服務(wù)的步驟要多些.客戶端,
沒什么變化,還是一個(gè)瘦客戶端,如將MidasLib加入到了項(xiàng)目引用就僅需部署一個(gè)單一的可執(zhí)行文
件.

服務(wù)端,必須部署數(shù)據(jù)庫驅(qū)動(dòng).及所選數(shù)據(jù)庫依賴的驅(qū)動(dòng)和文件.使用DBX4,確保發(fā)布
TSQLConnection組件和dbxconnections.ini及dbxgrivers.ini文件(可在C:\Documents and


Settings\All Users\ Documents\RAD

Studio\dbExpress\7.0 directory on Windows XP or in the C:\Users\Public\Documents\RAD
Studio\dbExpress\7.0中找到). dbxdrivers.ini文件指定所用驅(qū)動(dòng), DriverPackageLoader及
MetaDataPackageLoader(通常指向同一個(gè)包).對(duì)于BlackFishSQL,使用的DBXClientDriver140.bpl文件,需要
部署到服務(wù)端.更多關(guān)于部署B(yǎng)lashFishSQL信息見RAD Studio\7.0 目錄下的deploy_en.htm文件.

3.4. 重用已有的遠(yuǎn)程數(shù)據(jù)模塊

如果你有一個(gè)遠(yuǎn)程數(shù)據(jù)模塊類,也可以將其組合到新的DataSnap項(xiàng)目中來.但是必須要犧牲一些特性,尤其
是引入了COM.

首先,如果有一個(gè)你要遷移的DataSnap服務(wù)應(yīng)用程序,而不僅僅是一個(gè)遠(yuǎn)程數(shù)據(jù)模塊,你需要使用命令行
/unregister注銷DataSnap服務(wù).不做這步將無法注銷遠(yuǎn)程數(shù)據(jù)模塊.在遠(yuǎn)程數(shù)據(jù)模塊單元,移除initialization區(qū)
域.如果還希望這個(gè)單元在D2007及一下版本重用,可以使用編譯開關(guān):

{$IF CompilerVersion >= 20}

initialization

TComponentFactory.Create(ComServer, TRemoteDataModule2010,

Class_RemoteDataModule2010, ciMultiInstance, tmApartment);

{$IFEND}

end.

從項(xiàng)目中移除UpdateRegistry函數(shù)或用編譯開關(guān)修飾:

{$IF CompilerVersion >= 20}

class procedure UpdateRegistry(Register: Boolean;

const ClassID, ProgID: string); override;

{$IFEND}

最重要的變更—將項(xiàng)目轉(zhuǎn)換為無COM依賴的DataSnap服務(wù).-及移除類型庫(.ridl文件)和類型庫導(dǎo)入單元.這
無法通過編譯器開關(guān)修改,因此需要為D2007及一下版本和D2009及以上版本分別生成一個(gè)項(xiàng)目文件.在
TRemoteDataModule類中放一個(gè)TDSServerClass組件.最后,將所有的自定義方法都轉(zhuǎn)到
TRemoteDataModule單元的public節(jié)中.

4. DATASNAP 過濾器[FILTER]用法

本節(jié)將說明過濾器工作原理,及如何使用已存在的過濾器(如壓縮)或創(chuàng)建新的DataSnap過濾器.DataSnap
過濾器是一個(gè)特殊的DLL,攔截通訊流,在整個(gè)過濾器鏈中操作通訊流.所以本例中我們可以使用壓縮和解壓縮
過濾器,或記錄壓縮等.

必須在客戶端和服務(wù)端指定過濾器.在服務(wù)端,必須指定TDSTCPServerTransport組件的過濾器屬性列表.
在客戶端,必須在客戶端項(xiàng)目中引用過濾器單元文件.對(duì)于客戶端這就足夠了,因?yàn)槊總€(gè)DataSnap過濾器都會(huì)
自動(dòng)注冊(cè).

當(dāng)處理OnConnect事件時(shí),可以檢查用于連接的已注冊(cè)的過濾器,例如使用自定義的日志函數(shù)輸入日志信息,

procedure TServerContainer1.DSServer1Connect(

DSConnectEventObject: TDSConnectEventObject);

var

i: Integer;

begin

LogInfo('Connect ' + DSConnectEventObject.ChannelInfo.Info);

for i:=0 to DSConnectEventObject.Transport.Filters.Count-1 do

LogInfo(' Filter: ' +

DSConnectEventObject.Transport.Filters.GetFilter(i).Id);

end;


4.1. ZLIBCOMPRESSION FILTER

作為范例,我們使用已隨D2010提供的DataSnap過濾器.可用于在客戶端和服務(wù)端壓縮數(shù)據(jù)流.這里說的
ZlibCompression過濾器可以在DbxCompressionFilter單元找到.

TDSTCPServer和TDSHTTPService組件都有一個(gè)TTransportFiltersCollection類型的Filters屬性.點(diǎn)擊Filters
屬性后面的按鈕,編輯過濾器列表.在這個(gè)對(duì)話框中,我們可以加一個(gè)新的TTransportFilterItem,然后在Object
Inspector中設(shè)置FilterID和一些屬性.在下拉框中可以找到Delphi2010提供的ZLibCompression過濾器.

注意除了設(shè)置服務(wù)端TDSTCPServerTransport組件Filters屬性外,也需要在客戶端指定一個(gè)相應(yīng)的過濾器
(壓縮請(qǐng)求解壓縮應(yīng)答).這時(shí),我們進(jìn)需要將DbxCompressionFilter單元引用到ClientForm.其將自動(dòng)注冊(cè)一個(gè)
TTransportCompressionFilter并與服務(wù)端通訊.

如果沒有在客戶端添加DbxCompressionFilter單元引用,運(yùn)行客戶端后將會(huì)拋出異常信息:

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章