http://home.hoolee.com/~code6421/doc/datasnap.html
除了.NET 之外,今年的盛事大概就是DELPHI6 了,被Borland 標榜為新一代快速開發工具的DELPHI 6 擁有許多新的功能,除了WebSnap,WebService 之外,DELPHI 引以為傲的MIDAS 也改名為DataSnap,並且加強了許多功能,下面就是DELPHI 6 所增加的元件.
TConnectionBroker
TSharedConnection
TSoapConnection
TConnectionBroker
這個元件可以讓你的程式擁有動態切換Connection Object 的能力,例如,你在設計時期因為某些原因, 必須使用DCOMConnection,可是你的客戶卻需要使用SockConnection,這時你必須撰寫程式來動態切換RemoteServer屬性, 在DELPHI 6,這件事變的相當簡單我用了三種Connection Object(TWebConnection,TSocketConnection,TDCOMConnection),在她們之間放上了TConnectionBroker , 然後我將TClientDataSet 的ConnectionBroker 屬性設為ConnectionBorker1,這時我如果需要切換Connection Object, 我只需改變TConnectionBroker 的Connection 屬性就可以了,不需要考慮到TClientDataSet 的RemoteServer 屬性, 同時我們也可以利用TConnectionBroker.BeforeConnect Event
並利用ini or Registry 來動態切換Connection Object,
這樣你不需要重新Compiler 程式,就可以讓你的程式適應不同的網路型態了.
理想上是如此,但你很快就會發現,TConnectionBroker 有問題:
1. 拖放TDCOMConnection,TSocketConection,TWebConnection 至Form 上
2. 拖放TConnectionBroker 至Form 上,設定Connection 為TSocketConnection
3. 拖放TClientDataSet 至Form 上,設定ConnectionBroker跟ProviderName
4. 將TClientDataSet 的Active 設為Ture
你會發現TClientDataSet 的RemoteServer 已經變為TSocketConnection了,
很正常不是嗎? 當然不正常,ConnectionBroker 屬性已被清空了,也就是說,
ClientDataSet 與ConnectionBroker 的關聯已被切斷了,
那之後如果我們需要換為DCOMConnection 呢?
答案是你再也換不回來了,看以下的程式片斷: DBClient.pas
procedure TConnectionBroker.SetConnection(const Value: TCustomRemoteServer);
begin
…..略
(Datasets[i] as TCustomClientDataset).RemoteServer := FConnection;
end; 由這段程式碼我們可以知道,當你設定ConnectionBroker.Connection 屬性時, ConnectionBroker 會走訪所有相關的DataSet,並設定RemoteServer 屬性. DBClient.pas
procedure TCustomClientDataSet.SetRemoteServer(Value: TCustomRemoteServer);
begin
if Assigned(Value) and Assigned(ConnectionBroker) then
SetConnectionBroker(Nil);
..略..
end;
這段程式碼則告訴我們,當我們設定CustomClientDataSet.RemoteServer後,
ConnectionBroker 會被清空. DBClient.pas
procedure TCustomClientDataSet.SetConnectionBroker(const Value: TConnectionBroker);
begin
if Assigned(FConnectionBroker) then FConnectionBroker.UnRegisterClient(Self);
…略…
end;
這段程式碼告訴我們,當你設定ConnectionBroker 為Nil 時,它會先UnRegister 自己.
這三段程式碼的徵結點就在它們是完全'互斥' 的,
是的,你設了RemoteServer 將會使ConnectionBroker 失效,反之亦然!
那為何會有這樣的設計呢? 或許是為了保有RemoteServer 的正確值吧,
或取因為我們常常這樣寫:ClientDataSet.RemoteServer.AppServer.SomeFunction(…);
或許是VCL Team 希望我們升級程式更加方便吧,還是其它原因,他們決定保留RemoteServer 的值,不管如何, 它本身就是一個Bug 了,那我們也只能希望UpdatePack 來解決這個問題了,那現在呢? 文後會附上一個TOrpConnectionBroker 元件的Source,這個元件是繼承至TConnectionBroker 並redefine Connection property, 她可以暫時解決這個Bug,不過要注意的是,當你要取得AppServer 時,請由ConnectionBroker.GetServer or ClientDataSet.AppServer 來取得,不要由ClientDataSet.RemoteServer.AppServer 取得,因為它將是一個無效值.
TSharedConnection 如果你知道Dan Miser,那你也應該早就知道這個元件的用途,這個元件主要是用來管理多個RDM 的AP-Server, 早期的MIDAS 大多使用一個RDM,但隨著使用的人越多,程式也越大時,通常我們會分成幾個RDM,一方面容易管理, 一方面也減少資源的浪費.TSharedConnection 有兩個重要的屬性,ParentConnection 跟ChildName.ParentConnection 是連往Connection Object, 像是SocketConnection,WebConnection,DCOMConnection,特別注意的是TSharedConnection 並不支援SoapConnection, 主要原因是TSharedConnection 是運用COM Dispatch技術實作出來的,而且SoapConnection 是另一種Ap-Server, 因此不支援是正常的!
ChildName 則是你的子RDM 名稱,要達成這種架構,你必須稍稍修改你的Ap-Server,也就是如下的架構:為了讓TSharedConnection 能夠透過主RDM 找到子RDM,TSharedConnection 運用Disp ID,也就是說你的主RDM 內必須要有這樣的定義:
function Get_ChildRDM1: IChildRDM1; safecall;
property ChildRDM1: IChildRDM1 read Get_ChildRDM1 write Set_ChildRDM1;
這是代表你的AP-Server expose 一個屬性叫ChildRDM1,而TSharedConnection 則會運用Disp ID 來找到這個屬性,並傳回ChildRDM 物件, 接下來ClientDataSet 只須將RemoteServer 設為SharedConnection 就可以連往你的子RDM 了,這樣就可以管理多個RDM 了.
首先我們先建立一個Remote DataModule設定一下Database,Table,Provider(我用BCDEMOS Alias)
接下來我們要建立一個子RDM
在此選擇ciInternal(內部物件)
由於我們希望能使用Main RDM 的Database Component ,因此要加上這一行
(如果你將RDM 分開開發的話,你必需用call function 來取得Database Connection Object) 接著新增Table 跟Provider 至這個RDM
然後我們要稍稍修改這個RDM 的Source(紅色字是改變的部份)
unit Unit3;
{$WARN SYMBOL_PLATFORM OFF}
interface
uses
Windows, Messages, SysUtils, Classes, ComServ, ComObj, VCLCom, DataBkr,
DBClient, Project1_TLB, StdVcl, Provider, DB, DBTables;
type
TCoTestRDM = class(TRemoteDataModule, ICoTestRDM)
Table1: TTable;
DataSetProvider1: TDataSetProvider;
private
{ Private declarations }
protected
class procedure UpdateRegistry(Register: Boolean; const ClassID, ProgID: string); override;
public
{ Public declarations }
end;
var
FChildRDMFactory:TComponentFactory;
implementation
uses Unit2;
{$R *.DFM}
class procedure TCoTestRDM.UpdateRegistry(Register: Boolean; const ClassID, ProgID: string);
begin
if Register then
begin
inherited UpdateRegistry(Register, ClassID, ProgID);
EnableSocketTransport(ClassID);
EnableWebTransport(ClassID);
end else begin
DisableSocketTransport(ClassID);
DisableWebTransport(ClassID);
inherited UpdateRegistry(Register, ClassID, ProgID);
end;
end;
initialization
FChildRDMFactory := TComponentFactory.Create(ComServer, TCoTestRDM,
Class_CoTestRDM, ciInternal, tmApartment);
end.
接下來我們回到主RDM(Unit2),並開啟Type Library,在ICoTestSpeed3新增一個Property
將名稱取為TestRDM,Type 為ICoTestRDM(Unit3)
由於我們只需要Property Get,所以將Property Put 刪掉.
接下來完成MainRDM 的程式碼(紅字是修改的部份)
unit Unit2;
{$WARN SYMBOL_PLATFORM OFF}
interface
uses
Windows, Messages, SysUtils, Classes, ComServ, ComObj, VCLCom, DataBkr,
DBClient, Project1_TLB, StdVcl, Provider, DB, DBTables,Unit3;
type
TCoTestSpeed2 = class(TRemoteDataModule, ICoTestSpeed2)
Database1: TDatabase;
Table1: TTable;
DataSetProvider1: TDataSetProvider;
private
{ Private declarations }
protected
class procedure UpdateRegistry(Register: Boolean; const ClassID, ProgID: string); override;
function Get_TestRDM: ICoTestRDM; safecall;
public
{ Public declarations }
end;
implementation
{$R *.DFM}
class procedure TCoTestSpeed2.UpdateRegistry(Register: Boolean; const ClassID, ProgID: string);
begin
if Register then
begin
inherited UpdateRegistry(Register, ClassID, ProgID);
EnableSocketTransport(ClassID);
EnableWebTransport(ClassID);
end else begin
DisableSocketTransport(ClassID);
DisableWebTransport(ClassID);
inherited UpdateRegistry(Register, ClassID, ProgID);
end;
end;
function TCoTestSpeed2.Get_TestRDM: ICoTestRDM;
begin
Result := FChildRDMFactory.CreateComObject(nil) as ICoTestRDM;
end;
initialization TComponentFactory.Create(ComServer, TCoTestSpeed2, Class_CoTestSpeed2, ciMultiInstance, tmApartment); end. 接下來選擇Run(F9),讓我們的Ap-Server 註冊自己後我們開啟另一個Application, 實作Client 的部份,放入TSocketConnection ,並設定Address,ServerName.接下來放入TSharedConnection,設定ParentConnection為SocketConnection1 你就可以在ChildName 拉出你的子RDM 名稱
再放入一個ClientDataSet,設定RemoteServer 為SharedConnection1.
然後你就可以在ProviderName 內看到這個子RDM 所expose 的Provider
接下來你就知道如何做了吧?
呵,這真是一個好玩的特性,因為這樣,我們以後撰寫大程式更方便了.
TSoapConnection SOAP 是個年輕的通訊協定,各種功能也都還在發展中,DELPHI 6 也沒有在這方面缺席,除了WebService 之外, DataSnap 也允許你使用SOAP 來當成與AP-Server 通訊的協定,不過如果你使用SOAP,你必須將Remote DataModule 換成SOAP DataModule, 如果你需要Call Server Method 的話,自然的,WebService 是你唯一的選擇,由於SOAP 不需要COM,因此她是跨平台的最佳選擇, 相信Borland 會在Kylix Enterprise Edition 支援SOAP,這也代表著你可以撰寫Linux AppServer,Windows Client 反之亦然, 基本上使用SOAP 跟使用Remote DataMoudle 沒有太大的差別,不過有點要注意的是,SOAP 目前是State-less 的,也就是說PacketRecord 這類功能將無法由DataSnap 自動處理,你必須要自己寫程式來控制,如果你撰寫的Server Method 需要傳送某種特定型態資料時, 你必需撰寫Complex Type,這屬於WebService 的部份,並不在本文的範圍內,因此我就先跳過了,使用SoapConnection 只需要設定幾個屬性 就行了首先是URL 屬性,我們可以在On-line Help 看到這個範例:
http://DataHost.org/scripts/AppServer.dll/SOAP
使用SOAP 只需要設定URL 就可以了,如果你使用Proxy,那你還必須要設定Proxy 屬性,之後你就可以像使用其它Connection 一樣使用,
使用SOAP 除了上述的一些問題外,還有效能的問題,SOAP 可以用多種方式存在,,CGI 型式的SOAP Server 很容易產生效能的
問題,因為WebServer 必須為每次的Request 執行AP-Server,載入的時間就成為效能的殺手,
因此建議你使用ISAPI or Apache Shared DLL.
以下是建立SOAP Server 的部份
首先你要確認你的電腦是否有裝上IIS or IIS Personal Server
建立一個新的虛擬目錄
記得將執行的權限打開
再回到DELPHI,新增一個SOAP Application
選擇CGI(方便起見,以後可以再變)
再新增一個SOAP DataModule
放入Database,Session,Table,Provider(使用ISAPI 時Session 是個重要的課題) 之後將編譯後的執行檔放到最先建立的虛擬目錄中,接下來開始撰寫Client AP,開啟一個新專案, 將TSoapConnection(WebService Page)放進去
再來我們要設定SOAP 的URL 屬性
注意,格式是這樣子!
(URL)/(CGI or ISAPI file name)/soap/(Soap DataModule Interface Name)
http://192.168.0.1/Project2.exe/soap/ICoTestSoap
接下來將ClientDataSet 放入Form 中,設定RemoteServer再設定ProviderName
接下來你就知道了吧! 這是執行的畫面
由於SOAP 只需要HTTP,因此也可通過防火牆,這對很多專案來講,是一個好消息,但因為SOAP 目前還沒有規定RPC 的全部功能,
如果你選擇了SOAP,架構與效率都是很大的課題.以目前的測試來看,DELPHI 6 的DataSnap Bug 不少,
這也是我不將專案移植上來的原因,以下是我發現的Bug.
1. TConnectionBroker 運作不正常
2. ClientDataSet 在Brief-case Modal 時不會在Close 時存回Local CDS File
3. Variant 在使用TDataTime 時會有些問題(SOAP Server)!
但不管如何,我還是認為DataSnap 加強了很多我們以往要寫程式才能做到的部份,希望在Update Pack 能夠修正這些Bug!
TOrpConnectionBroker Component Source
unit OrpConnectionBroker;
interface
uses
Windows, Messages, SysUtils, Classes, DB, DBClient;
type
TProtectedAccessClientDataSet=class(TCustomClientDataSet);
TOrpConnectionBroker = class(TConnectionBroker)
private
procedure SetConnection(Value:TCustomRemoteServer);
function GetConnection:TCustomRemoteServer;
{ Private declarations }
protected
{ Protected declarations }
public
{ Public declarations }
published
property Connection:TCustomRemoteServer read GetConnection write SetConnection;
{ Published declarations }
end;
procedure Register;
implementation
function TOrpConnectionBroker.GetConnection:TCustomRemoteServer;
begin
Result:=inherited Connection;
end;
procedure TOrpConnectionBroker.SetConnection(Value:TCustomRemoteServer);
var
FOpendDS,FClosedDS:TList;
I:Integer;
begin
FOpendDS:=TList.Create;
FClosedDS:=TList.Create;
try
for I:=0 to DataSetCount-1 do
begin
if (DataSets[I] as TCustomClientDataSet).Active then
begin
FOpendDS.Add(DataSets[I]);
(DataSets[I] as TCustomClientDataSet).Close;
end
else
FClosedDS.Add(DataSets[I]);
end;
if Connected then Connected:=False;
inherited Connection:=Value;
for I:=0 to FOpendDS.Count-1 do
begin
TProtectedAccessClientDataSet(FOpendDS[I]).ConnectionBroker:=Self;
TProtectedAccessClientDataSet(FOpendDS[I]).Open;
end;
for I:=0 to FClosedDS.Count-1 do
TProtectedAccessClientDataSet(FClosedDS[I]).ConnectionBroker:=Self;
finally
FOpendDS.Free;
FClosedDS.Free;
end;
end;
procedure Register;
begin
RegisterComponents('DataSnap', [TOrpConnectionBroker]);
end;
end.