Delphi 2009仍包含了原來的DataSnap,但是也提供了一個新的遠程處理和多層技術(shù),它部分基于dbExpress架構(gòu)。這個新技術(shù)仍然叫做DataSnap。但是為了避免混淆通常叫做“DataSnap 2009”。
建立一個DataSnap 2009演示
在我講述更多細節(jié)之前,讓我從建立一個簡單的三層數(shù)據(jù)庫應用向?qū)а菔鹃_始。這將幫助我們了解一些知識點,也包括與上一版本的不同點。
建立一個服務(wù)器
首先建立一個DataSnap 2009服務(wù)器應用程序。這可以是標準的VCL應用程序,再添加一個Server module(在New Items對話框的Delphi Files頁,而不是Multitier頁):
對于Server Module(你也可以用標準的Data Module)通常添加dbExpress組件用來連接數(shù)據(jù)服務(wù)器,添加一個數(shù)據(jù)集提供器輸出指定的數(shù)據(jù)集:
object IBCONNECTION: TSQLConnection
ConnectionName = 'IBCONNECTION'
DriverName = 'Interbase'
LoginPrompt = False
Params.Strings = (
'DriverName=Interbase'
'Database=C:\Program Files\...\Data\Employee.GDB')
end
object EMPLOYEE: TSQLDataSet
CommandText = 'EMPLOYEE'
CommandType = ctTable
SQLConnection = IBCONNECTION
end
object DataSetProviderEmployee: TDataSetProvider
DataSet = EMPLOYEE
end
在過去也是用非常相似的方法建立這個Server Module。與之區(qū)別的就是需要添加三個新的組件以提供配置和鏈接。這三個組件是:
DSServer,主服務(wù)器配置組件,這是其它所有DataSnap 2009組件一起工作的組件。
DSServerClass,一個在公開的每個類時需要的組件。這個組件不提供可用的類,而是產(chǎn)生用于遠程客戶機調(diào)用的類對象,換句話說,DSServerClass組件將引用具有公共接口的類。
DSTCPServerTransport,定義傳輸協(xié)議(這個協(xié)議只有在Delphi2009中直接可用)和設(shè)置TCP/IP端口。
在此演示中,這些組件在服務(wù)器端主窗體中。設(shè)置如下:
object DSServer1: TDSServer
AutoStart = True
HideDSAdmin = False
OnConnect = DSServer1Connect
OnDisconnect = DSServer1Disconnect
end
object DSTCPServerTransport1: TDSTCPServerTransport
PoolSize = 0
Server = DSServer1
BufferKBSize = 32
end
object DSServerClass1: TDSServerClass
OnGetClass = DSServerClass1GetClass
Server = DSServer1
LifeCycle = 'Session'
end
我們稍后講述這些組件的一些屬性的細節(jié)。在上面的設(shè)置中沒有列出TCP/IP端口的值,是因為我沒有改變它的默認值211。
你只需要編寫“類工廠”在代碼,以使DSServerClass1鏈接到Server Module公開的提供器:
procedure TFormFirst3Tier2009Server.
DSServerClass1GetClass(DSServerClass: TDSServerClass;
var PersistentClass: TPersistentClass);
begin
PersistentClass := TDSFirst3TierServerModule;
end;
這是服務(wù)器需要的全部。我給上面的方法添加了一個記錄語句,在DSServer1的Onconnect和OnDisconnect事件中。
再次說明,它不需要任何注冊。只要運行它,在Delphi IDE中可以用Run | Run Without Debugging命令 。這樣可以在設(shè)計期同時建立客戶端程序并鏈接到服務(wù)器。
第一個客戶端
現(xiàn)在我們有了一個可用的服務(wù)器,現(xiàn)在開始建立第一個客戶端。在DataSnap2009客戶端中,我們需要使用一個SQLConnect組件聯(lián)合一個新的DataSnap驅(qū)動,設(shè)置正確的TCP/IP端口。
下一步,需要一個DSProviderConnection組件,用來指向服務(wù)器類,用ServerClassName屬性。這不是服務(wù)器的中間類工廠,但是是類工廠的實際目標,在這個例子里,它是TDSFirst3TierServerModule類。
對于傳統(tǒng)的DataSnap程序,ClientDataSet能使用提供器獲?。ú⒏拢┻h程數(shù)據(jù)集。首先,必須設(shè)置ClientDataSet的RemoteServer屬性,從下拉列表中選擇DSProviderConnection1組件。下一步,設(shè)置ProviderName屬性選為DataSetProviderEmployee,這個列表列出了所有遠程Data Module公開的所有DataSetProvider組件。
下面列出了所有組件的屬性設(shè)置,包括一個用來在DBGrid中顯示數(shù)據(jù)表的DataSource:
object SQLConnection1: TSQLConnection
DriverName = 'Datasnap'
end
object DSProviderConnection1: TDSProviderConnection
ServerClassName = 'TDSFirst3TierServerModule'
SQLConnection = SQLConnection1
end
object ClientDataSet1: TClientDataSet
ProviderName = 'DataSetProviderEmployee'
RemoteServer = DSProviderConnection1
end
object DataSource1: TDataSource
DataSet = ClientDataSet1
end
這是一個初步演示所有要做的?,F(xiàn)在先運行服務(wù)器后運行客戶端,你可以單擊客戶端的Open按鈕以看到數(shù)據(jù)表的數(shù)據(jù),也可以注意到服務(wù)據(jù)端顯示了記錄。
從DataSnap到DataSnap2009
比較傳統(tǒng)的DataSnap應用程序,有一些關(guān)鍵的不同,更多相關(guān)于架構(gòu)和部署多于實際代碼,必須注意:
服務(wù)器開發(fā)再沒有了復雜的COM,即使客戶端可以仍然使用過去的sockets,在服務(wù)器上必須有一個socket-to-Com映射服務(wù)?,F(xiàn)在服務(wù)器和客戶端通信直接通過TCP/IP。
另外,現(xiàn)在不必注冊專門的服務(wù)器,也不需要在服務(wù)器上運行任何輔助服務(wù)。所有服務(wù)器必須公開一個所有客戶端能搜索到的TCP/IP端口。
必須手動運行服務(wù)器端程序,或建立一個系統(tǒng)服務(wù)。過去,COM支持服務(wù)器在需要時自動起動服務(wù)器端程序。
服務(wù)器的實現(xiàn)對于組件而言稍微復雜,但是只是在幕后加了少許代碼,以對應COM。
客戶端的實現(xiàn)幾乎相同,同樣需要標準的SQLConnection組件,代替一個特定的連接對象。
在服務(wù)器端, TDSServerModule繼承自TDataModule,包含了IAppServer接口(過去基于COM的TRemoteDataModule也使用這個接口)并且打開了$MethodInfo編譯指示器。
在客戶端,dbExpress驅(qū)動是100%原生態(tài)Delphi驅(qū)動。你不需要在客戶機上部署任何DLL,即使你使用dbExpress連接。
最需要注意的是當關(guān)閉服務(wù)器程序時。不像在COM架構(gòu)里,將警告你要持續(xù)連接。而DataSnap2009服務(wù)器將看似關(guān)閉,但實際是等到所有客戶端剩余連接關(guān)閉后。然而,甚至等所有聯(lián)接全部關(guān)閉它仍然運行在內(nèi)存中,即使主窗體已經(jīng)關(guān)閉。你需要用任務(wù)管理器(或過程瀏覽器)來終止服務(wù)器。你或許想著關(guān)閉所有客戶端程序就足夠了,但是不夠的:Delphi IDE會自動打開一個連接連接到服務(wù)器,用來瀏覽公開的類和方法。在關(guān)閉它之前請確認關(guān)閉了所有到服務(wù)器的連接。
添加服務(wù)器方法
在過去,服務(wù)器端寫的方法可以由客戶端調(diào)用。它們基于COM,所以必須向類型庫添加接口并在服務(wù)器對象中實現(xiàn)它們,在客戶端通過COM指派接口來調(diào)用。在DataSnap2009中遠程方法調(diào)用,或服務(wù)器方法調(diào)用是基于Delphi的RTTI的。注意,參數(shù)是以dbExpress參數(shù)類型傳遞的,而不是Delphi語言類型。
你可以有多個服務(wù)器端類以公開方法,但在接下來我已經(jīng)建立這個簡單的項目里,我向服務(wù)器模塊類(在服務(wù)器端)添加了一個外部方法,代碼如下:
type
TDSFirst3TierServerModule = class(TDSServerModule)
IBCONNECTION: TSQLConnection;
EMPLOYEE: TSQLDataSet;
DataSetProviderEmployee: TDataSetProvider;
private
{ Private declarations }
public
function GetHello: string;
end;
function TDSFirst3TierServerModule.GetHello: string;
begin
Result := 'Hello from TDSFirst3TierServerModule at '
+ TimeToStr (Now);
end;
為達到遠程調(diào)用,必須連接到通過DSServerClass公開方法的類。(前提是我們已經(jīng)完成了數(shù)據(jù)庫部分)。第二個要求是啟用類的$MethodInfo編譯指示器,但這已經(jīng)在基TDSServerModule類中聲明。這意味著,在實踐中,我們所要做的就是添加一個公共方法給服務(wù)器模塊,一切將工作。
怎么在客戶端應用程序調(diào)用這些服務(wù)器方法呢?有兩個基本的選擇。第一個是使用新的SqlServerMethod組件,調(diào)用服務(wù)器方法像存儲過程一樣。第二個是在客戶端程序中生成一個代理類,用此代理類調(diào)用方法。
在下面的客戶端演示中,我實現(xiàn)了這兩種方法。第一個,向客戶端窗體上加一個SqlServerMethod組件,并綁定到連接,選擇設(shè)置ServerMethodName屬性的值(列表中列出了所有可用的IAppServer接口方法),并檢查Params屬性的值。下面列出了此組件屬性設(shè)置的代碼(這實際上包括進行檢查時,參數(shù)的示例調(diào)用結(jié)果):
object SqlServerMethod1: TSqlServerMethod
GetMetadata = False
Params = <
item
DataType = ftWideString
Precision = 2000
Name = 'ReturnParameter'
ParamType = ptResult
Size = 2000
Value = 'Hello from TDSFirst3TierServerModule...'
end>
SQLConnection = SQLConnection1
ServerMethodName = 'TDSFirst3TierServerModule.GetHello'
end
傳統(tǒng)的String類型成了一個2000字符的String類型參數(shù),設(shè)置完SqlServerMethod組件的屬性后,該程序可以以帶輸入?yún)?shù)(此處沒有)和輸出參數(shù)(作為結(jié)果)的存儲過程或查詢形式來調(diào)用:
procedure TFormFirst3Tier2009Client.btnHelloClick(
Sender: TObject);
begin
SqlServerMethod1.ExecuteMethod;
ShowMessage (SqlServerMethod1.Params[0].Value);
end;
為了更容易的編寫調(diào)用代碼,我們可以使用我前面提到的第二種方式,在客戶端建立一個代理類。要做到這一點,我們可以要求Delphi IDE解析服務(wù)器的類的接口,并為它的本地代理類,可以右擊SQLConnection組件,選擇 Generate Datasnap client classes命令。Delphi將生成下面的類單元(我省略了構(gòu)造器和自毀器代碼):
type
TDSFirst3TierServerModuleClient = class
private
FDBXConnection: TDBXConnection;
FInstanceOwner: Boolean;
FGetHelloCommand: TDBXCommand;
public
constructor Create(
ADBXConnection: TDBXConnection); overload;
constructor Create(
ADBXConnection: TDBXConnection;
AInstanceOwner: Boolean); overload;
destructor Destroy; override;
function GetHello: string;
end;
function TDSFirst3TierServerModuleClient.GetHello: string;
begin
if FGetHelloCommand = nil then
begin
FGetHelloCommand := FDBXConnection.CreateCommand;
FGetHelloCommand.CommandType :=
TDBXCommandTypes.DSServerMethod;
FGetHelloCommand.Text :=
'TDSFirst3TierServerModule.GetHello';
FGetHelloCommand.Prepare;
end;
FGetHelloCommand.ExecuteUpdate;
Result := FGetHelloCommand.Parameters[0].
Value.GetWideString;
end;
生成的代碼沒有用高級的SqlServerMethod組件,而是直接調(diào)用低級的dbExpress實現(xiàn)對象,像TDBXCommand類。
有了這個可用的代理類,客戶端可以用更多的友好的方式調(diào)用服務(wù)器方法,雖然我們需要創(chuàng)建一個代理類的實例。此代碼不完全與上面基于SqlServerMethod組件的代螞相同:
procedure TFormFirst3Tier2009Client.btnHelloClick(
Sender: TObject);
begin
with TDSFirst3TierServerModuleClient.Create(
SQLConnection1.DBXConnection) do
try
ShowMessage (GetHello);
finally
Free;
end;
end;
如果代碼實際上比前一版本更長,這是因為我們調(diào)用的方法沒有參數(shù),從而使語言綁定代碼少有關(guān)。然而,有一個現(xiàn)成的代理對象,我們可以這樣寫:
ShowMessage (ServerProxyObject.GetHello);
非數(shù)據(jù)庫DataSnap服務(wù)器下的SESSIONS和THREADING
如果在DataSnap2009中直接使用IAppServer接口成為最常見的方式,它可以用于數(shù)據(jù)庫之外的遠程方法的調(diào)用。你也可以用同樣的技術(shù)訪問數(shù)據(jù)庫或執(zhí)行數(shù)據(jù)庫操作而不用IAppServer接口,如果只是想從數(shù)據(jù)庫讀取數(shù)據(jù)這是最好的。如果你想在客戶端修改數(shù)據(jù)并更新到服務(wù)器,使用自定義的方法可能比現(xiàn)成的IAppServer接口更加繁瑣,通過ClientDataSet和DataSetProvider組件實現(xiàn)。
在任何情況下,在下面這第二個例子中,我想創(chuàng)建一個最小的服務(wù)器并公開幾個簡單的類。在接下來的章節(jié)中我將用這個服務(wù)器來探索服務(wù)器內(nèi)存管理和服務(wù)器(和客戶端)線程的幾個相關(guān)問題。
第一個服務(wù)器類(有兩個方法)我想公開在DsnapMethodServer項目里,代碼如下:
{$MethodInfo ON}
type
TSimpleServerClass = class(TPersistent)
public
function Echo (const Text: string): string;
function SlowPrime (MaxValue: Integer): Integer;
end;
{$MethodInfo OFF}
第一個方法的代碼只是簡單的回顯輸入的內(nèi)容,并重復內(nèi)容的最后部分,第二個方法是執(zhí)行最流行的慢計算。下面是兩個方法的代碼:
function TSimpleServerClass.Echo(
const Text: string): string;
begin
Result := Text + '...' +
Copy (Text, 2, maxint) + '...' +
Copy (Text, Length (Text) - 1, 2);
end;
function TSimpleServerClass.SlowPrime(
MaxValue: Integer): Integer;
var
I: Integer;
begin
// counts the prime numbers below the given value
Result := 0;
for I := 1 to MaxValue do
begin
if IsPrime (I) then
Inc (Result);
end;
end;
我省略了從上面的代碼登錄服務(wù)器的額外代碼。
服務(wù)器應用程序只有一個單元,只定義了主窗體和兩個服務(wù)器端類。窗體包含了常用的DataSnap組件,一個DSServer和一個DSTCPServerTransport,為兩個要公開的類加上兩個DSServerClass組件。編譯完服務(wù)器并啟動后,Delphi已經(jīng)用新的客戶端SQLConnection組件創(chuàng)建了一個客戶端代理。下面是客戶端代理類:
type
TSimpleServerClassClient = class
private
FDBXConnection: TDBXConnection;
FInstanceOwner: Boolean;
FEchoCommand: TDBXCommand;
FSlowPrimeCommand: TDBXCommand;
public
constructor Create(
ADBXConnection: TDBXConnection); overload;
constructor Create(
ADBXConnection: TDBXConnection;
AInstanceOwner: Boolean); overload;
destructor Destroy; override;
function Echo(Text: string): string;
function SlowPrime(MaxValue: Integer): Integer;
end;
在客戶端,在按鈕的OnClick事件中在建立代理類的實例后,根據(jù)需要調(diào)用服務(wù)器方法Echo:
procedure TFormDsnapMethodsClient.btnEchoClick(
Sender: TObject);
begin
if not Assigned (SimpleServer) then
SimpleServer := TSimpleServerClassClient.Create (
SQLConnection1.DBXConnection);
Edit1.Text := SimpleServer.Echo(Edit1.Text);
end;
點擊按鈕,文本“Marco”將通過服務(wù)器調(diào)用轉(zhuǎn)換為“Marco...arco...co”。這一個完整的例子,顯示了如何建立沒有數(shù)據(jù)庫訪問,并沒有使用IAppServer接口。在Delphi中,這不是唯一的方法調(diào)用技術(shù),還可以用SOAP,基于Socket的應用程序,或第三方工具……但有這樣的擴展功能在遠程數(shù)據(jù)庫訪問能力上無疑是一個附屬品。
令我聚焦于這個例子的一個原因是,它有助于澄清DataSnap2009的相關(guān)功能。其中之一就是如何實現(xiàn)服務(wù)器端對象與客戶端代理鏈接或?qū)崿F(xiàn)服務(wù)器端方法調(diào)用。下面是演示項目的第二個服務(wù)器類,很好地演示了一個服務(wù)器對象跟蹤自身狀態(tài):
{$MethodInfo ON}
type
TStorageServerClass = class(TPersistent)
private
FValue: Integer;
public
procedure SetValue(const Value: Integer);
function GetValue: Integer;
function ToString: string; override;
published
property Value: Integer read GetValue write SetValue;
end;
{$MethodInfo OFF}
讀取器和賦值器簡單地讀取和寫入本地字段,ToString函數(shù)同時返回一個值和對象標示的哈希碼:
function TStorageServerClass.ToString: string;
begin
Result := 'Value: ' + IntToStr (Value) +
' - Object: ' + IntToHex (GetHashCode, 4);
end;
我將用這個方法指出服務(wù)器對象的生命周期怎樣工作。這個類的屬性定義只在服務(wù)器端,沒有公開到客戶端。相應的代理接口變?yōu)椋ㄒ迫チ怂接杏蚝蜆藴实臉?gòu)造器和自毀器):
type
TStorageServerClassClient = class
public
procedure SetValue(Value: Integer);
function GetValue: Integer;
function ToString: string;
注意,編譯此類將產(chǎn)生如下警告,除非手動給方法加上Override:
Method 'ToString' hides virtual method of base type 'TObject'
整個例子指出當多個客戶端同時使用同一個服務(wù)器時會發(fā)生什么。DataSnap2009是行為根據(jù)DSServerClass組件的LifeCycle屬性值而決定。
服務(wù)器對象的生命周期
DataSnap 2009的服務(wù)器對象的生命周期取決于與之相關(guān)的DSServerClass組件的相應設(shè)置。LifeCycle屬性有以下三個設(shè)置(這是當DSServer對象打開時從DSServerClass組件讀取來的,在運行時修改將無效):
Session 這表示服務(wù)器將為每一個客戶端Socket鏈接創(chuàng)建不同的對象,即,每個客戶端將有一個獨立的服務(wù)器對象。服務(wù)器對象將在連接關(guān)閉時被釋放。當服務(wù)器對象是一個數(shù)據(jù)模塊時每個客戶端將有獨自的狀態(tài)和單獨的數(shù)據(jù)庫訪問,或許有自己的數(shù)據(jù)庫鏈接組件。這是默認的設(shè)置。
Invocation 表示每次調(diào)用服務(wù)器方法時將創(chuàng)建一個新的服務(wù)器對象(和銷毀)。這是一個典型的無狀態(tài)行為,這是服務(wù)器有極高的可擴展性,但相同的數(shù)據(jù)將被多次重復讀取。
Server 表示一個單獨的共享服務(wù)器。每個客戶端將使用同一個服務(wù)器對象實例,相同數(shù)據(jù),可能會引起數(shù)據(jù)同步的問題(不同的客戶端調(diào)用是由不同的服務(wù)器線程執(zhí)行)。訪問共享服務(wù)器對象必須由同步技術(shù)所保護(例如使用新的TMonitor記錄)。
除了使用這些默認設(shè)置,你可以使用DSServerClass的OnCreateInstance和OnDestroyInstance事件自定義服務(wù)器對象的創(chuàng)建和自毀。這可以用來實現(xiàn)服務(wù)器端對象池。
客戶端啟動服務(wù)器并打開多個連接
作為一個實際的例子,DsnapMethods從客戶端的單個實例創(chuàng)建了多個客戶端鏈接(使用多個實例,將產(chǎn)生相同的結(jié)果),你可以創(chuàng)建窗體的多個實例,窗體上有SQLConnection組件并存儲了第一次被使用時創(chuàng)建的客戶端代理的本地實例。這是很容易實現(xiàn)的,因為客戶端和服務(wù)器程序在同一臺電腦上。
為了做到這一點,我已經(jīng)向服務(wù)器程序主窗體單元添加了一個全局變量,用來記錄DSServerClass的LifeCycle屬性:
var
ParamLifeCycle : string; procedure TFormDsnapMethodsServer.DSServerClass2GetClass(
DSServerClass: TDSServerClass;
var PersistentClass: TPersistentClass);
begin
DSServerClass2.LifeCycle := ParamLifeCycle;
Log ('LifeCycle: ' + DSServerClass2.LifeCycle);
PersistentClass := TStorageServerClass;
end;
ParamLifeCycle的值由服務(wù)器程序的命令行參數(shù)來初始化,下面的代碼在項目文件源代碼的開始部分: begin
if ParamCount > 0 then
ParamLifeCycle := ParamStr(1);
Application.Initialize;
服務(wù)器上加上這些代碼,客戶端主窗體(沒有鏈接,鏈接在輔助窗體中配置)的RadioGroup設(shè)置了以下的值:
object rgLifeCycle: TRadioGroup
ItemIndex = 0
Items.Strings = (
'Session'
'Invocation'
'Server')
end
當點擊一個按鈕,客戶端程序?qū)⒆x取當前值并做為參數(shù)傳遞到服務(wù)器(注意不能重復運行服務(wù)器,因為你不能在同一臺電腦上同時打開兩個應用程序通過同一端口監(jiān)聽同一個Socket):
procedure TFormDsmcMain.btnStartServerClick(
Sender: TObject);
var
aStr: AnsiString;
begin
Log (rgLifeCycle.Items[rgLifeCycle.ItemIndex]);
aStr := 'DsnapMethodsServer.exe ' +
rgLifeCycle.Items[rgLifeCycle.ItemIndex];
WinExec (PAnsiChar (aStr), CmdShow);
end;
客戶端主窗體還有一個按鈕用來創(chuàng)建第二個窗體,它們將在關(guān)閉時銷毀(在它們的OnClose事件處理程序中),關(guān)閉到服務(wù)器的特定鏈接。另一個按鈕用來記錄客戶端窗體的當前狀態(tài):
procedure TFormDsmcMain.btnUpdateStatusClick(
Sender: TObject);
var
I: Integer;
begin
for I := 0 to Screen.FormCount - 1 do
if Screen.Forms[I].ClassType = TFormDsmcClient then
Log (IntToStr (I) + ': ' +
Screen.Forms[I].ToString);
end;
當為輔助窗體調(diào)用ToString時,將返回連接的服務(wù)器對象的狀態(tài),調(diào)用它的公共ToString函數(shù):
function TFormDsmcClient.ToString: string;
begin
InitStorageServer;
Result := StorageServer.ToString;
end;
作為第一個可執(zhí)行的例子,我用了默認的Session創(chuàng)建服務(wù)器,打開了兩個客戶端窗體。設(shè)置值為3和4,查看所有狀態(tài),得到以下結(jié)果:
Session
1: Value: 3 - Object: 1C38400
2: Value: 4 - Object: 1C384E0
第二次執(zhí)行,我設(shè)置了Invocation,并兩次查看狀態(tài),得到以下結(jié)果:
Invocation
1: Value: 0 - Object: 1D185B0
2: Value: 0 - Object: 1D18490
1: Value: 0 - Object: 1D185C0
2: Value: 0 - Object: 1D185D0
注意看到每次執(zhí)行都得到一個新的對象,而對象的狀態(tài)總是0(并且在每次調(diào)用后對象將馬上銷毀,任何設(shè)置也將馬上丟失)。不用說,這只適用于無狀態(tài)操作。
最后,執(zhí)行相同的步驟(設(shè)置值為3和4)使用Server,這次每個客戶端窗體都用了同一個服務(wù)器對象,并適用了最后一次值設(shè)置:
Server
1: Value: 4 - Object: 1E08490
2: Value: 4 - Object: 1E08490
換句話說,這個練習表明,這個理論是正確的!在演示中探索生命周期的設(shè)置,我們還看到了一個客戶端啟動它需要的(本地)服務(wù)器,和一個客戶端同時發(fā)出多個鏈接到服務(wù)器的例子。
移植舊的DataSnap
在探索完一些DataSnap2009的功能之后,讓我們返回到傳統(tǒng)的使用場景,一個多層數(shù)據(jù)庫應用。我們已經(jīng)看到了創(chuàng)建全新的DataSnap數(shù)據(jù)庫應用的全部過程?,F(xiàn)在讓我們把重點同樣重要的問題上:將一個原有的DataSnap(或MIDAS)應用程序移植到新的架構(gòu)。
做為實例,我決定用由Delphi2005創(chuàng)建的應用程序ThinPlus,它展示了一些DataSnap的功能,讓我來做一個更完整的例子。這個例子仍然關(guān)注于從一個使用原生的基于Socket架構(gòu)的客戶端對COM接口的服務(wù)器的調(diào)用應該做什么。新的例子(包括服務(wù)器和客戶端項目)在ThinPlus2009目錄下。
要注意的是移植舊的DataSnap應用到新的架構(gòu)是一個有趣的操作,但這不是強制性的。舊的DataSnap服務(wù)器和客客戶端完全可以在DataSnap2009下編譯和運行。
(程序在“精通Delphi2005”和以前版本如“精通Delphi7”中有詳細的描述。在這里我將只提供它的一些功能的概括。這些書當然可以給你一個更廣泛的DataSnap和以前MIDAS的原始特征,大多仍然在Delphi 2009版本中可用。)
移植服務(wù)器
移植服務(wù)器項目,我列出了這些步驟:
·移除了遠程數(shù)據(jù)模塊單元的initialization區(qū)域,叫做AppsRDM。刪除了調(diào)用TComponentFactory類的構(gòu)造函數(shù)的代碼。
·也移了遠程數(shù)據(jù)模塊單元的TAppServerPlus 類的UpdateRegistry類方法。
·在這一點上,我可以忽略遠程數(shù)據(jù)模塊uses子句中與COM和ActiveX相關(guān)的單元:ComServ, ComObj, VCLCom, StdVcl。
·接下來必須移除自定義IAppServerPlus接口的引用,這接口由項目用來提供自定義服務(wù)器方法(這個接口在項目類型庫中定義)。
·從項目和磁盤中刪除類型庫和RIDL文件(這是項目打開時創(chuàng)建的)。也必須移除uses子句中引用類型庫的單元。
·在遠程數(shù)據(jù)模塊類中,把唯一的服務(wù)器方法(Login)從protected區(qū)域挪動到public區(qū)域,并移除它的safecall修飾符。由于TRemoteDataModule類已經(jīng)在$MethodInfo打開下編譯的,所以不需要向項目單元添加這個聲名。
·最后,要向程序的主窗體上添加常用的三個組件(Server,Server Class,Server transport),把它們鏈接在一起,并從服務(wù)器類組件的OnGetClass事件中返回TAppServerPlus。
這是從老的DataSnap服務(wù)器升級到新的DataSnap2009所有要做的。這似乎很多,但它實際上是相當快的。現(xiàn)在我們來看客戶端,做了一些自定義操作。
修改客戶端
移植客戶端通常比移植服務(wù)器要簡單一些。關(guān)鍵步驟是移除鏈接組件,取而代之的是一個SQLConnection和一個DSProviderConnection,并使ClientDataSet組件指向這個新的鏈接組件。
比較特殊的就是修改了調(diào)用服務(wù)器方法Login的代碼。這發(fā)生在Connection組件的OnAfterConnection事件中,我現(xiàn)在把它移到SQLConnection組件的相應的事件:
procedure TClientForm.SQLConnection1AfterConnect(
Sender: TObject);
begin
// was: ConnectionBroker1.AppServer.
// Login (Edit2.Text, Edit3.Text);
SqlServerMethod1.ParamByName('Name').AsString :=
Edit2.Text;
SqlServerMethod1.ParamByName('Password').AsString :=
Edit3.Text;
SqlServerMethod1.ExecuteMethod;
end;
這個調(diào)用傳遞登錄信息到服務(wù)器。服務(wù)器驗證信息,如果通過,將向客戶端提供數(shù)據(jù)。密碼檢查很容易但可能很有趣。這是Login服務(wù)器方法:
procedure TAppServerPlus.Login(
const Name, Password: WideString);
begin
if Password <> Name then
raise Exception.Create (
'Wrong name/password combination received');
ProviderDepartments.Exported := True;
ServerForm.Add ('Login:' + Name + '/' + Password);
end;
注意到這個過程將返回服務(wù)器異常并清晰的顯示在客戶端。如下圖:
前面提到的升級ThinPlus客戶端和服務(wù)器到DataSnap2009的步驟,即使這些都是一些比較復雜的DataSnap方案和一些自定義。其中包括手動獲取數(shù)據(jù)包,使用主/細結(jié)構(gòu),運行參數(shù)化查詢,以數(shù)據(jù)包形式傳遞額外數(shù)據(jù),自定義遠程登錄我都講過了。
這是值得注意的功能,即使他很簡短,它可以幫助你在從沒使用過(或不太了解)DataSnap的情況下看到它的能力。對于已經(jīng)使用過它的可以看出如何簡單的移植到新架構(gòu)。服務(wù)器程序中定義了一個主/細結(jié)構(gòu),基于下面(各自的)提供器的設(shè)置,數(shù)據(jù)源用來指向主數(shù)據(jù)集,細數(shù)據(jù)集指向數(shù)據(jù)源:
object ProviderDepartments: TDataSetProvider
DataSet = SQLDepartments
end
object SQLDepartments: TSQLDataSet
CommandText = 'select * from DEPARTMENT'
SQLConnection = SQLConnection1
end
object DataSourceDept: TDataSource
DataSet = SQLDepartments
end
object SQLEmployees: TSQLDataSet
CommandText =
'select * from EMPLOYEE where dept_no = :dept_no'
DataSource = DataSourceDept
Params = <
item
Name = 'dept_no'
ParamType = ptInput
end>
SQLConnection = SQLConnection1
end
在客戶端,第一個ClientDataSet與提供器相連,而第二個ClientDataSet指向第一個ClientDataSet的一個字段:
object cds: TClientDataSet
FetchOnDemand = False
PacketRecords = 5
ProviderName = 'ProviderDepartments'
RemoteServer = DSProviderConnection1
object cdsDEPT_NO: TStringField...
object cdsDEPARTMENT: TStringField...
...
object cdsSQLEmployees: TDataSetField
FieldName = 'SQLEmployees'
end
end
object cdsDet: TClientDataSet
DataSetField = cdsSQLEmployees
end
兩個ClientDataSet的數(shù)據(jù)顯示在兩個DBGrid中。注意程序從每個數(shù)據(jù)包中只獲取5條記錄(在 PacketRecords屬性中設(shè)置),并在第一個數(shù)據(jù)包后停止獲?。ㄒ驗镕etchOnDemand屬性為False)即使表格沒有顯示滿。下面是打開鏈接后客戶端運行界面的截圖:
接下來的數(shù)據(jù)包手動來獲取,點擊相應的按鈕:
procedure TClientForm.btnFetchClick(Sender: TObject);
begin
btnFetch.Caption := IntToStr (cds.GetNextPacket);
end;
在按鈕上顯示了每個數(shù)據(jù)包中有多少條記錄。如果記錄數(shù)足夠,這個數(shù)字將是5,否則將顯示剩余記錄數(shù),當記錄全部讀完后將顯示0。DBGrid顯示的記錄數(shù)將會隨著獲取的數(shù)據(jù)而增加。還可以用bntRecCount按鈕顯示到目前為止獲取了多少條記錄。
點擊Query按鈕顯示客戶端第二個窗體,操作另一個客戶端數(shù)據(jù)集。這個數(shù)據(jù)集與服務(wù)器端定義的參數(shù)化查詢相連:
object SQLWithParams: TSQLDataSet
CommandText =
'select * from employee where job_code = :job_code'
Params = <
item
DataType = ftString
Name = 'job_code'
ParamType = ptInput
Value = 'Eng'
end>
SQLConnection = SQLConnection1
end
客戶端程序有一個列表框,顯示了各個部門名字,用來做為參數(shù)傳遞到服務(wù)器。注意在寫這段代碼前首先要更新參數(shù)的定義,可以在設(shè)計期用ClientDataSet的屬性編輯器來實現(xiàn)。這是用于在客戶端上執(zhí)行遠程參數(shù)化查詢的調(diào)用:
procedure TFormQuery.btnParamClick(Sender: TObject);
begin
cdsQuery.Close;
cdsQuery.Params[0].AsString := ComboBox1.Text;
cdsQuery.Open;
...
服務(wù)器端,當這個查詢執(zhí)行后提供器的OnGetDataSetProperties事件向返回的數(shù)據(jù)包添加額外的信息:
procedure TAppServerPlus.
ProviderQueryGetDataSetProperties(Sender: TObject;
DataSet: TDataSet; out Properties: OleVariant);
begin
Properties := VarArrayCreate([0,1], varVariant);
Properties[0] := VarArrayOf(['Time', Now, True]);
Properties[1] := VarArrayOf([
'Param', SQLWithParams.Params[0].AsString, False]);
end;
請注意,仍然可以用變體數(shù)組作為參數(shù),即使DataSnap2009的傳輸機制已不同。在客戶端,btnParamClick事件程序從數(shù)據(jù)包中獲取這兩個額外屬性:
Caption := 'Data sent at ' + TimeToStr (
TDateTime (cdsQuery.GetOptionalParam('Time')));
Label1.Caption := 'Param ' +
cdsQuery.GetOptionalParam('Param');
DataSnap的一些功能已移植到新版本,但ThinPlus2009(和用Delphi6寫的原始版本相比幾乎沒變)已經(jīng)足夠達到我的目的了:顯示DataSnap的能力和如何簡單的將DataSnap的甚至很復雜的程序遷移到DataSnap2009。
結(jié)語
在本文中,我介紹了一個組件庫在Delphi2009中重要更新:一個新的DataSnap架構(gòu)不借助于COM來構(gòu)建多層應用程序。可以用DataSnap2009來編寫數(shù)據(jù)庫應用程序,也可以輕松的調(diào)用任何服務(wù)器端方法。
多層應用程序基于sockets,不需要在客戶端或服務(wù)器端注冊COM簡化了部署并使它很容易與防火墻一起工作。DataSnap 2009利用現(xiàn)有的DataSnap架構(gòu),一個很強大的多層架構(gòu),可以把一些業(yè)務(wù)邏輯部署在中間層,開放了一個更現(xiàn)代(和原生的)方法。Delphi2009中新的DataSnap為將來的擴展提供了基礎(chǔ),如HTTP傳輸協(xié)議。
最近發(fā)布的Delphi Prim(CodeGear的一個新的Delphi for .net開發(fā)環(huán)境)可以創(chuàng)建ataSnap2009客戶端應用程序。例如:可以創(chuàng)建一個ASP.Net項目可以鏈接到由Delphi下編寫并編譯的DataSnap2009服務(wù)器數(shù)據(jù)庫。
它可能將進一步擴展,可使DataSnap用于移動數(shù)據(jù),并響應其它情況下的情求,但當前的TCP / IP架構(gòu)已經(jīng)是一個堅實的基礎(chǔ),使DataSnap2009比起它的前一版本和其它許多競爭對手成為更靈活的解決方案。
作者簡介
這個白皮書是Marco Cantù為易博龍公司所寫的,是最暢銷的《精通Delphi》系列書籍的作者。這些內(nèi)容都來源于他的最新書《Delphi 2009手冊》,http://www.marcocantu.com/dh2009。你可以了解Marco在他的博客(http://blog.marcocantu.com)并通過Email聯(lián)系到他:marco.cantu@gmail.com。
關(guān)于譯者
我是一個Delphi的使用者和愛好者,最近一直在為自己的工作和生活而困惑著。Delphi的中文資料也太少了,很多都是E文的,唉,很無語。我的E文水平也不怎么的,不好意思說水平二字了,這是我翻譯的《DataSnap2009白皮書》,很多都是直譯,可能有很多都是錯的,因我E文實在有點拿不出手了,哪位高手能指出其中之錯, 我萬分感謝!
作者原文:http://edn.embarcadero.com/article/images/39227/The-New-Datasnap-in-Delphi-2009_WP.pdf。
在此對原文作者道歉!