2008 年 11 月 的封存

平行處理機制,平行Linq和Delphi Prism

在上篇我從一些蛛絲馬跡來猜測Delphi除了朝原生64位元發展之外,也可能會在非同步,平行執行方向進行突破的發展。當這篇文章貼出之後,很快的我的朋友就問我『這樣的』Delphi會在什麼時候出現,說實話我不知道,因為這也是我從一些蛛絲馬跡『猜測』的,所以我當然不知道,只能等時間來告訴我們了,呵呵。
Delphi Prism程式語言中提供了許多非同步,平行執行的機制,除了上次我們看到的async和parallel之外,當我們使用這些非同步,平行執行的機制結合PFX 框架時,也能夠很方便的在使用Linq時也享受平行執行的好處,例如下面是傳統使用Linq的一個典型,簡單的範例:

class method ConsoleApp.Linq;
var
  words : sequence of string;
begin
  words := [‘嗨’, ‘世界’ , ‘Linq’, ‘Parallel Linq’ , ‘Delphi Prism’, ‘Delphi 2009’, ‘BCB 2009’, ‘3rdRail’];
  var rQuery := from word in words select word;

  for each s in rQuery do
    Console.WriteLine(s);

  Console.ReadLine;
end;

當我們使用Delphi Prism編譯並且執行之後,會看到如下的結果:
http://blufiles.storage.live.com/y1pnfguIQKXzsE1F49XruVvqOLio8MYDAdvlGGt7aDrP2UdtPAi5PKF_AcX1PVgZisw

我們從上圖的執行結果可以看到,它輸出的結果次序和我們在程式碼中定義sequence的次序是一樣。
但是如果我們稍微修改一點程式碼,在words之後呼叫擴展方法AsParallel如下所示:

class method ConsoleApp.PLinq;
var
  words : sequence of string;
begin
  words := [‘嗨’, ‘世界’ , ‘Linq’, ‘Parallel Linq’ , ‘Delphi Prism’, ‘Delphi 2009’, ‘BCB 2009’, ‘3rdRail’];
  var rQuery := from word in words.AsParallel select word;

  rQuery.ForAll<string>(word -> Console.WriteLine(word));
  Console.ReadLine;
end;

那麼我們會看到如下的執行結果:
http://blufiles.storage.live.com/y1piJJHrhRgrHyDrKVst5KDd5kJbI-DL-bzA5_85VIIIdXjmXkUNeNYbLbKPucIyLJahttp://blufiles.storage.live.com/y1pFfkk9_1ena1VTyZedMmv5gXjo4_miYp14SPNbsdlE7H2ZAHcplG_1DSSMfteZgcK
 
從上圖中可以發現每次執行平行查詢時執行的結果次序都不同,而且輸出的次序也不是程式碼中定義的次序,這個證明了當使用Delphi Prism的平行機制結合PFX框架時的確是以多個平行執行的核心來處理的。
使用.NET Reflector反組編PLinq方法也可以看到它的確是呼叫PFX框架中提供的平行處理機制:

class method ConsoleApp.PLinq;
begin
ParallelEnumerable.ForAll<String>(ParallelQuery.AsParallel<String>(array of([‘嗨’, ‘世界’, ‘Linq’, ‘Parallel Linq’, ‘Delphi Prism’, ‘Delphi 2009’, ‘BCB 2009’, ‘3rdRail’])), delegate (word: String)
    begin
        Console.WriteLine(word)
    end;
    Console.ReadLine
end;

和呼叫Linq方法是截然不同的。

class method ConsoleApp.Linq;
begin
    var rQuery: IEnumerable<String> := array of([‘嗨’, ‘世界’, ‘Linq’, ‘Parallel Linq’, ‘Delphi Prism’, ‘Delphi 2009’, ‘BCB 2009’, ‘3rdRail’]);
    if (rQuery <> nil) then
        {pseudo} goto Label_0058;
    rQuery.GetEnumerator;
    var enumerator: IEnumerator<String> := nil;
    if (enumerator <> nil) then
        while enumerator.MoveNext do begin
            var s: String := enumerator.Current;
            Console.WriteLine(s)
        end;
    Console.ReadLine
end;

Delphi Prism提供的先進非同步,平行執行機制突然讓原生Delphi 32/64有了新的發展方向。

廣告

4 則迴響

使用Delphi開發分散式JSON應用系統

在12月我又受委託將舉行4場有關DataSnap和分散式應用系統開發的技術研討會, 在這次的研討會中我將討論下面相關的內容:

  • 什麼是JSON?
  • Delphi和JSON程式設計
  • 如何昇級/轉換COM/DCOM/COM+的分散式架構為JSON分散式架構
  • 如何使用DataSnap 2009開發新一代的分散式架構應用程式
  • DataSnap 2009的JSON分散式架構管理機制
  • JSON伺服器生命週期
  • 開發可異動的JSON分散式應用系統
  • 結合Web應用程式和JSON分散式架構


同樣的, 這是免費的技術研討會, 日期是在12/09~12/12, 如果您有興趣的話, 您可以在下面的URL報名參加:

http://www.sinter.com.tw/codegear/seminars/delohi200920081120.html

4 則迴響

非同步,平行執行技術,從Delphi到Delphi Prism,再到未來的Delphi?

前一陣子Delphi的現任總架構師Allen Bauer討論了許多有關非同步和平行處理的文章,而更早MS也在.NET平台上推出Microsoft Parallel Extensions to Framework 3.5套件,開始進行對於平行開發技術的演進,這個趨勢是想見而知的,因為現今的CPU都是多核心,每一個核心又都支援多執行緒執行的能力,因此軟體技術如何搭配硬體的進步,進而發揮硬體的計算能力是軟體突破的重要方向之一。如果各位嗅覺靈敏的話,從Allen的一系列文章以及他的研究方向來看,各位應該可以開始猜測未來Delphi的發展方向了。
除了這些蛛絲馬跡之外,CodeGear最近又宣佈了Delphi Prism,Delphi Prism除了是一個獨立的產品之外,也將和Delphi/C++Builder 2009整合為RAD Studio 2009,提供在.NET方面的解決方案。不過Delphi Prism並不是Delphi.NET,Delphi Prism是.NET平台上使用類似Delphi語法的程式語言,並且包含了許多先進的程式語言功能。有趣的是Delphi Prism已經在程式語言本身加入了許多非同步,平行執行功能,當結合Microsoft PFX framework時,能夠自動使用PFX中的平行執行技術。

今天我在CodeGear的部落格上看到了Andreano討論有關Delphi Prism的文章,其中就有討論到Delphi Prism程式語言中使用關鍵字async來建立非同步執行的範例,我覺得蠻有意思的,因為這代表現在可以開始討論Delphi Prism了。在Andreano的範例中,他使用了async來宣告一個方法,在Delphi Prism中使用async關鍵字宣告的方法就合格成為一個非同步執行的方法,讓我使用一個範例來說明一下。
在下面的程式碼中宣告了5個類別方法,以及兩個使用async宣告的非同步方法CallLoops和CallParallelLoops:

type
  ConsoleApp = class
  public
    class method Main;
    class method CallAsyncMethod;
    class method CallAsyncWithParallel;
    class method Linq;
    class method PLinq;
    method CallLoops(iID : Integer) ; async;
    method CallParallelLoops(iID : Integer); async;
  end;

接著我在類別方法Main中呼叫其他四個類別方法:

class method ConsoleApp.Main;
begin
  ConsoleApp.CallAsyncMethod; 
  ConsoleApp.CallAsyncWithParallel;
  ConsoleApp.Linq;
  ConsoleApp.PLinq;
end;

在CallAsyncMethod中,它在一個迴圈中呼叫了使用async宣告的CallLoops方法,因此.NET會產生四個執行緒來執行CallLoops:

class method ConsoleApp.CallAsyncMethod;
begin
  Console.WriteLine(‘開始執行非同步方法’);

  with lConsoleApp := new ConsoleApp() do
  begin
    for i : Integer := 0 to 4 do
       lConsoleApp.CallLoops(i);
  end;
  Console.WriteLine(‘結束執行非同步方法’);
  Console.ReadLine;
end;

而CallLoops方法只是很簡單的執行一個迴圈,列印訊息並且使用Thread在每一次迴圈暫時暫停0.1秒。

method ConsoleApp.CallLoops(iID : Integer) ;
begin
  for i : Integer := 0 to 4 do
  begin
    Console.WriteLine(‘執行緒’ + iID.ToString + ‘ : ‘ + i.ToString);
    System.Threading.Thread.Sleep(100);
  end;
    Console.WriteLine(‘執行緒’ + iID.ToString + ‘執行完成’);
end;

讓我們看看這個執行的結果,從下圖中讀者可以看到.NET果然以四個執行緒,以不定的次序執行每一個執行緒,所以在Delphi Prism中要開發非同步執行的方法非常的簡單的,只要使用關鍵字async來宣告方法即可。

http://blufiles.storage.live.com/y1peVkQA8uAo_Fv5OSB6PgOgi86eB7sFjTSjxNwMu1dcbMswRKjjjGjB6JJJu_YRiI8

然而讀者如果仔細觀察上圖的執行狀態,會發現雖然四個執行緒以不定的次序執行,但是每一個執行緒在執行CallLoops方法的迴圈時仍然是以順序的方式在執行迴圈,這代表CallLoops雖然是在獨立的執行緒中執行,但仍然沒有使用平行執行技術。但在Delphi Prism中我們只需要使用一個parallel關鍵字就可以立刻使用Microsoft PFX framework提供的平行技術。
例如下面的CallAsyncWithParallel使用了幾乎和CallAsyncMethod一樣的程式碼,只是CallAsyncWithParallel呼叫了非同步方法CallParallelLoops:

class method ConsoleApp.CallAsyncWithParallel;
begin
  Console.WriteLine(‘開始執行非同步, 並行方法’);

  with lConsoleApp := new ConsoleApp() do
  begin
    for i : Integer := 0 to 4 do
       lConsoleApp.CallParallelLoops(i);
  end;
  Console.WriteLine(‘結束執行非同步, 並行方法’);
  Console.ReadLine;
end;

而CallParallelLoops也幾乎和CallLoops一樣,只是注意CallParallelLoops在呼叫for迴圈時使用了另外一個關鍵字parallel

method ConsoleApp.CallParallelLoops(iID : Integer) ;
begin
  for parallel i : Integer := 0 to 4 do
  begin
    Console.WriteLine(‘執行緒’ + iID.ToString + ‘ : ‘ + i.ToString);
    System.Threading.Thread.Sleep(100);
  end;
    Console.WriteLine(‘執行緒’ + iID.ToString + ‘執行完成’);
end;

在Delphi Prism中如果使用關鍵字parallel而且開發人員參考了Microsoft PFX framework,那麼Delphi Prism便會編譯出如下的程式碼:

method ConsoleApp.@AsyncProc_0_CallParallelLoops(iID: Int32);
beginvar c__1: <>c__0 := new <>c__0;
c__1.iID := iID;
Parallel.&For(0, 5, 1, new Action<Int32, ParallelState>(c__1,<CallParallelLoops>__0));
Console.WriteLine(String.Concat('執行緒', iID.ToString, '執行完成'))
end;

在上面的程式碼中Delphi Prism編譯器會自動產生PFX的Parallel.For的程式碼提供平行執行for迴圈的能力。
下圖是CallAsyncWithParallel執行的結果,讀者可以仔細觀察,CallAsyncWithParallel也產生了四個執行緒來執行,但是每一個執行緒在執行CallParallelLoops迴圈時卻不是以順序的方式來執行,而是使用平行的方式執行for迴圈。
http://blufiles.storage.live.com/y1pOShWV2i4BG0HEKm6wd1hBLqfXEwbjFJiabr50xz62y69ZbFYromv4M1tcIYSiMI8

 從上面的討論中我們可以瞭解Delphi Prsim提供了非常方便的非同步和平行處理的機制,讓開發人員能夠在結合PFX時開發出平行執行架構。
在下次的文章中讓我們再看看Delphi Prism在支援Linq和平行Linq時是多麼的方便。

4 則迴響

DBX框架篇 : 第1章 初探DBX4框架 – 2

1-2 執行SQL命令 : TDBXCommand類別
在DBX4框架中TDBXCommand類別是使用來執行SQL命令的,在上一小節中我們已經在開發人員必須呼叫TDBXConnection的CreateCommand方法來建立TDBXCommand物件,再藉由TDBXCommand物件來執行SQL命令。
下面的表格列出了TDBXCommand類別中最常使用的方法和特性:
http://blufiles.storage.live.com/y1pOwd9QTSD0WhDnFhU2el9IqMPIA7RlNxOTDYcMemmOWaXBIqQ9tp8rG8njaG-qikD

要使用TDBXCommand物件來執行SQL命令,開發人員必須把 SQL命令指定給Text特性,例如

aCommand.Text := ‘Select count(*) from DBXTESTTABLE’;

如果要執行擁有動態參數的SQL命令,那麼動態參數必須使用’?’字元來代表,例如下面的SQL命令藉由在執行時指定“研討會名稱”欄位的實際數值從DBXTESTTABLE資料表中選擇符合條件的結果資料集:

aCommand.Text := ‘Select * from DBXTESTTABLE where “研討會名稱” = ? ’;

那麼如何在應用程式執行時實際設定SQL命令中的動態參數值呢? 這就必須使用TDBXParameter類別。

1-2-1 代表SQL敘述中動態參數的類別 : TDBXParameter
TDBXParameter類別物件可代表SQL命令中的每一個以’?’字元代表的動態參數,因此SQL命令中擁有多少個以’?’字元代表的動態參數,那麼開發人員就必須為每一個以’?’字元代表的動態參數建立一個相對應的TDBXParameter物件,然後設定TDBXParameter物件中正確的參數特性值,最後再呼叫TDBXCommand物件的Parameters特性的AddParameter方法把每一個TDBXParameter物件加入到TDBXCommand物件,最後才能呼叫TDBXCommand的ExecuteQuery或是ExecuteQuery真正的執行SQL命令。
TDBXParameter類別也擁有許多的方法和特性,下面的表格列出了比較重要的方法和特性:
http://blufiles.storage.live.com/y1pVu3Ef8VfE3lg2WYfYak2I71tQbe5FOjwpAKtAlbaIxfd5jDO_K4FcOQC3p6QOQrS

現在讓我們看看如何使用TDBXParameter來執行動態SQL,假設我們需要執行下面的SQL敘述:

select * from SEMINARS where “主講人” = ?

由於在上面的SQL敘述中包含一個以’?’字元代表的動態參數,因此我們需要建立一個TDBXParameter物件,設定它的資料型態和數值再加入到TDBXCommand物件中才能呼叫ExecuteQuery來執行此SQL敘述。下面的程式碼展示了如何執行上面的動態SQL敘述:
001    procedure TForm5.執行SQL命令;
002    var
003      aCommand : TDBXCommand;
004      aParameter : TDBXParameter;
005      aReader: TDBXReader;
006    begin
007      if (Assigned(dbx4Conn)) then
008      begin
009        aCommand := dbx4Conn.CreateCommand;
010        try
011          aCommand.CommandType := TDBXCommandTypes.DbxSQL;
012          aCommand.Text := Self.edtSQL.Text;
013          aParameter := aCommand.CreateParameter;
014          aParameter.DataType := TDBXDataTypes.WideStringType;
015          aParameter.Value.AsString := Self.edt主講人.Text;
016          aCommand.Parameters.AddParameter(aParameter);
017          aReader := aCommand.ExecuteQuery;
018          while (aReader.Next) do
019          begin
020            Self.ListBox1.Items.Add(aReader.Value[‘名稱’].GetWideString + ‘ : ‘ + aReader.Value[‘主講人’].GetWideString);
021          end;
022        finally
023          FreeAndNil(aReader);
024          FreeAndNil(aCommand);
025        end;
026      end;

012行設定了TDBXCommand要執行的SQL敘述之後,013行建立TDBXParameter物件,014行設定TDBXParameter物件的資料型態,015行設定實際動態參數的數值,016行把TDBXParameter物件加入到TDBXCommand物件,017行執行動態SQL敘述,018行之後藉由TDBXReader物件來處理結果資料集,在下一小節中將說明TDBXReader類別。
下圖是上面程式碼執行動態SQL敘述的結果:
 
http://blufiles.storage.live.com/y1pACQpal0WSGSlEH2V5_-Ooq_90pKNDZXDEaIscT96nWcp-X3Zw8nGjLq8MYYstqVX
圖1-4使用TDBXCommand和TDBXParameter執行動態SQL敘述

在使用TDBXCommand物件執行命令時,開發人員必須指定TDBXCommand物件執行的命令種類,而這些TDBXCommand物件能夠執行的命令種類是由TDBXCommandTypes類別定義。TDBXCommandTypes類別定義了數個常數值(const)來定義不同的命令種類,開發人員需要使用它來設定TDBXCommand物件的CommandType特性,類似上面的011行程式碼。
下面的表格整理了TDBXCommandTypes定義的常數值以及這些常數值使用的意義:
http://blufiles.storage.live.com/y1pq3CdkGLWl73tb0Iv6Zg0VKM-m2r3ulE4Gt0w6y-oGwrDLT2hz8AguvRnp2h1z5-e

從上表可知,TDBXCommand不但可以執行SQL命令,執行後端預儲程序,存取資料庫元資料,更可以執行DataSnap 2009中加入的DataSnap伺服器輸出的方法。例如,假設我們有一個DataSnap 2009開發的JSON伺服器,它輸出了一個GetSpeakerCount方法能夠回傳後端Speaker資料表中所有記錄的總數值,那麼我們就可以使用下面的程式碼在用戶端使用TDBXCommand物件來執行遠端JSON伺服器中的GetSpeakerCount方法並且取得執行結果:

function TDSServerModule2Client.GetSpeakerCount: Integer;
begin
  if FGetSpeakerCountCommand = nil then
  begin
    FGetSpeakerCountCommand := FDBXConnection.CreateCommand;
    FGetSpeakerCountCommand.CommandType := TDBXCommandTypes.DSServerMethod;
    FGetSpeakerCountCommand.Text := ‘TDSServerModule2.GetSpeakerCount’;
    FGetSpeakerCountCommand.Prepare;
  end;
  FGetSpeakerCountCommand.ExecuteUpdate;
  Result := FGetSpeakerCountCommand.Parameters[0].Value.GetInt32;
end;

我們只需要如上設定TDBXCommand物件的CommandType特性值為TDBXCommandTypes.DSServerMethod命令種類之後就可以執行遠端服務方法了。
在稍後討論如何開發DataSnap 2009分散式應用程式時會再詳細的說明如何在用戶端執行遠端JSON伺服器輸出的方法。

1-2-2 存取結果資料集 : TDBXReader類別

當開發人員使用TDBXCommand執行命令時,TDBXCommand的ExecuteQuery方法會回傳TDBXReader物件,在DBX4框架中TDBXReader通常使用來代表執行命令之後回傳到用戶端的結果物件,如果開發人員執行的結果是資料集,那麼就可以使用TDBXReader來取得結果資料集,如果執行命令的結果是單一數值,那麼也會以結果資料集的形式儲存在TDBXReader物件中,開發人員仍然可以使用TDBXReader物件來存取結果數值。
TDBXReader是一個單向,只能往前存取的資料結構型態,類似TDataSet。當開發人員取得TDBXReader物件並且欲存取其中的執行結果時,一開始先要呼叫它的Next方法以便把cursor指到TDBXReader物件中第一筆的資料位置,那麼在處理完每一筆資料之後再呼叫Next指到下一筆資料,而在每一筆資料位置上,開發人員是藉由存取TDBXReader的Value特性來取得每一個欄位的執行結果,下面的表格整理了TDBXReader類別中最重要的方法和特性的說明:
http://blufiles.storage.live.com/y1pPsK8v6_zrKz7zWTiRAwjeRSu0nmP4YkwdrRNatMu6JV2lHVlGXAEYcZKSpSo-3hw

TDBXReader的Value特性是一個複載的特性,開發人員可以使用名稱或是索引值來存取TDBXReader物件中每一筆資料中每一個欄位其中的數值,在TDBXReader中Value有如下的複載的宣告:

    property Value[const Ordinal: TInt32]: TDBXValue read GetValue; default;
    property Value[const Name: UnicodeString]: TDBXValue read GetValueByName; default;

例如在前面執行SQL敘述的範例中,我們藉由TDBXReader複載的Value來存取結果資料集中’名稱’欄位的數值如下:

aReader.Value[‘名稱’].GetWideString;

如果’名稱’欄位是SEMINARS資料表中的第二個欄位,那麼我們也可以使用索引值的方式來如下它的數值如下:

aReader.Value[1].GetWideString;

因此使用TDBXReader物件通常擁有下面的兩個型式:

  • 使用TDBXReader存取單一執行結果:

aDBXReader.Next;
aDBXReader. Value[0].Get資料型態

  • 使用TDBXReader存取多個執行結果:

while (aDBXReader.Next)
begin
  aDBXReader. Value[0].Get資料型態;
  aDBXReader. Value[1].Get資料型態;
  …
end;

或是:
while (aDBXReader.Next)
begin
    for iCount := 0 to aDBXReader. ColumnCount – 1 do
    begin
      aDBXReader. Value[iCount].Get資料型態;
      aDBXReader. Value[iCount].Get資料型態;
    end;
  …
end;

此外,當開發人員實際在使用DBX4開發資料庫或是分散式應用程式時,應該大都是使用DBX4的元件,只有在很少的情形下才會直接使用TDBXReader,不過如果實的遇上必須使用TDBXReader時,那麼TDBXReader可以轉換為TDataSet嗎? 答案是可以的,在DXB4框架中TCustomSQLDataSet類別提供了一個重載建構函式,這個建構函式可以接受TDBXReader參數並且根據它建立TDataSet物件,如此一來就可以完成轉換TDBXReader物件為TDataSet物件的須要。下面即即是此TCustomSQLDataSet重載的建構函式宣告:

TCustomSQLDataSet = class(TWideDataSet)

    constructor Create(AOwner: TComponent; DBXReader: TDBXReader; AOwnsInstance: Boolean); reintroduce; overload;

因此我們可以使用如下簡單的程式碼就可以轉換TDBXReader物件為TDataSet物件:

  aDataSet := TCustomSQLDataSet.Create(nil, aCommand.ExecuteQuery, True);

在取得了TDataSet物件之後,它就更容易使用而且能夠和DBX4的元件一起工作了。

1-2-3 TDBXValue類別
在上面的討論中,當我們使用TDBXReader的Value特性存取執行結果數值時,實際上是取得TDBXValue類別物件,每一個TDBXValue物件中包含一個執行結果數值,或是包含一個執行結果資料集欄位的數值。
TDBXValue類別定義了許多GetXXXX方法讓開發人員取得執行結果數值,而XXXX就是不同的執行結果數值的資料型態。例如,如果執行結果是整數值,那麼就呼叫GetInt32方法來取得結果,如果是字串數值,就呼叫GetWideString,開發人員可以藉由判斷TDBXValue的ValueType特性來判斷其中數值的資料型態。
下面的表格整理了TDBXValue類別重要的方法和特性:
http://blufiles.storage.live.com/y1pXy2Uf6HW0zqMIJFQ1v6M19mbPxhBiLlZgsWtvqsHOTbuoD50ZA5eR3qPDOzUmlMH

因此在一般的應用中,我們經常會使用如下的程式碼樣例來使用TDBXValue:

  case aDBXValue.ValueType.DataType of
    TDBXDataTypes.DateType:
      Result := IntToStr(aDBXValue.GetDate);
    TDBXDataTypes.WideStringType:
      Result := aDBXValue.GetWideString;
    TDBXDataTypes.Int32Type:
      Result := IntToStr(aDBXValue.GetInt32);
    TDBXDataTypes.DoubleType:
      Result := FloatToStr(aDBXValue.GetDouble);
    else
      …
  end;

發表留言

DBX框架篇 : 第1章 初探DBX4框架

DBX技術從Delphi 6開始歷經了2次重大的改變,從Delphi 6一直到Delphi 2006是DBX技術的第1階段,在這段DBX的目的是在發展出一個輕量級,可跨平台的通用資料存取框架,以取代BDE/IDAPI成為Delphi/C++Builder的標準資料存取技術。在這個階段中,DBX技術是由VCL框架中的DBX相關類別/介面和DBX驅動程式組成,由於當時的DBX驅動程式是使用C/C++撰寫的,因此VCL中相關的DBX類別/介面也大量的使用了指標和靜態連結的方式來實作,這樣的架構在日後的擴展和維護上比較麻煩。
從Delphi 2006之後,DBX進入第二個階段,這是因為DBX除了提供Win32的存取能力之外,也需要在.NET框架 2.0之後也改變架構的ADO.NET之上加入DBX的技術,以取代BDP.NET技術,同時DBX在此時也決定採取了另外一個重大的變革,那就是將重新使用Object Pascal程式語言來實作所有的DBX驅動程式。由於DBX驅動程式改用了Object Pascal實作,因此在VCL框架中和DBX驅動程式相關的程式碼再也不需要大量使用指標來呼叫其中的函式,這個優勢讓VCL中的DBX框架有了重新使用更好的方式來實作的機會,再加上Object Pascal也能夠執行於.NET平台之後(CodeGear在.NET平台上的Object Pascal稱為Delphi程式語言),DBX框架也終於可以使用相同的設計架構執行於Win32和.NET平台,因此第二階段的DBX改以現代化的類別和介面的架構為設計,實作基礎,讓DBX真正成為完全以物件導向思維設計出來的框架,在讀者隨著本書的章節閱讀之後也將對DBX框架有更完整的瞭解。
現在讓我們先開始討論DBX4框架中的骨架類別,並且從這些最基礎而重要的類別中逐漸展開我們對於DBX4框架的掌握。
任何資料存取技術都由一些固定的樣版類別或是物件來完成,例如首先需要連結到資料來源,需要執行SQL命令,需要存取資料庫中的物件資訊等,所有的資料存取技術都是由這些基本的類別/物件提供的服務再逐漸的提供更多豐富的功能。DBX也不例外,在DBX框架中也是以這些基本而重要的類別為核心架構而成,例如:

  •     TDBXConnection類別提供資料來源連結的能力,
  •     TDBXCommand類別提供執行SQL命令的功能
  •     TDBXMetaDataProvider類別提供存取和處理資料庫物件的功能

要使用DBX連結資料來源,首先我們需要取得代表資料來源連結的物件: TDBXConnection,但是如何取得TDBXConnection物件以便和資料來源進行連結?又如何使用它來進行其他的工作? 要回答這些我們需要從TDBXConnectionFactory類別開始說起。

1-1 使用DBX4技術連結資料庫 – TDBXConnectionFactory類別
DBX框架中的TDBXConnectionFactory類別嚴格的說是一個抽象父類別,它的類別靜態方法GetConnectionFactory可以回傳一個從它繼承下來的實體類別,這實體類別將實作如何載入DBX驅動程式和資料來源連結設定等重要的資訊。在目前的DBX4框架實作中,這個實體類別是TDBXIniFileConnectionFactory。
TDBXIniFileConnectionFactory會根據dbxdrivers.ini檔案中的驅動程式設定以載入驅動程式,並且根據dbxconnections.ini檔案中設定的資訊來設定連結資訊。
因此在DBX4要和資料來源連結,首先我們可以使用下面的程式碼來取得TDBXIniFileConnectionFactory物件:
TDBXConnectionFactory.GetConnectionFactory.
接著在TDBXConnectionFactory類別中的GetConnection方法即可讓我們取得可連結資料來源的TDBXConnection物件。GetConnection有兩個原型宣告,它是複載(override)方法:

    function GetConnection( const ConnectionName : UnicodeString;
                            const UserName: UnicodeString;
                            const Password: UnicodeString) : TDBXConnection; overload;
    function GetConnection(ConnectionProperties : TDBXProperties) : TDBXConnection; overload;

第一個GetConnection重載方法接受一個連結名稱,資料來源的登錄使用者名稱和密碼來回傳TDBXConnection物件,第二個GetConnection重載方法則接受一個TDBXProperties參數而回傳TDBXConnection物件。
讓我們先解釋如何使用第一個GetConnection方法,它的第一個參數ConnectionName其實就是讀者在Delphi中使用DataExplorer建立的連結別名,例如下圖所示,假設我們有一個InterBase的連結別名IB_UCONNECTION,那麼我們就可以使用下面的程式碼來呼叫GetConnection以取得連結到InterBase的TDBXConnection物件:

http://blufiles.storage.live.com/y1pw3XknILfNjkvrE03i0OHM3_PqiTqEdl3PPOjoDfP3MSYUKW-qLpooGMw7co_CU_N
圖1-1 DataExplorer顯示存在dbxconnections.ini中的連結資訊

var
  dbx4Conn : TDBXConnection;
begin
  …
  dbx4Conn :=  TDBXConnectionFactory.GetConnectionFactory.GetConnection(
    ‘IB_UCONNECTION’, ‘sysdba’, ‘masterkey’);
  …

或是建立下面的TDBXProperties物件,設定必要的特性值之後再傳遞TDBXProperties物件給GetConnection來取得和資料來源的連結:
var
  dbxProp : TDBXProperties;
  dbxConn : TDBXConnection;
begin
  dbxProp := TDBXProperties.Create;
  try
    dbxProp[TDBXPropertyNames.ConnectionName] := ‘IB_UCONNECTION’;
    dbxProp[TDBXPropertyNames.UserName] := ‘sysdba’;
    dbxProp[TDBXPropertyNames.Password] := ‘masterkey’;
    dbxConn :=  TDBXConnectionFactory.GetConnectionFactory.GetConnection(dbxProp);
    Assert(Assigned(dbxConn), TDBXPropertyNames.ConnectionName +’連結失敗’);
  finally
    dbxProp.Free;
    dbxConn.Close;
    dbxConn.Free;
  end;

上面程式碼中的TDBXPropertyNames類別中定義了所有資料來源可能需要設定的特性值名稱,例如下面是TDBXPropertyNames中的一些定義:

  TDBXPropertyNames = class
  Const
    ConnectionName              = ‘ConnectionName’;    
    UserName                    = ‘User_Name’;        
    Password                    = ‘Password’;         
    …
end;

上面的程式碼就設定了TDBXProperties物件要連結的資料來源名稱,登錄的使用者名稱和密碼等資訊,GetConnection方法就根據這些資訊取得資料來源的連結並且回傳代表此連結的TDBXConnection物件。

TDBXConnectionFactory類別最重要的功能當然就是回傳TDBXConnection物件,因為有了TDBXConnection物件之後開發人員就可以使用它來執行各種工作,例如執行SQL命令,存取資料來源物件等,稍後本書都會說明。除此之外,TDBXConnectionFactory類別也可以讓開發人員取得所有資料來源的連結資訊,也就是圖1-1中DataExplorer顯示的資訊,以及所有dbExpress驅動程式資訊以及目前執行中的應用程式中註冊的dbExpress驅動程式資訊。

例如TDBXConnectionFactory的GetConnectionItems可以取得圖1-1中的所有資料來源連結:
 
  aSL := TStringList.Create;
  TDBXConnectionFactory.GetConnectionFactory.GetConnectionItems(aSL);

而GetDriverNames可以取得所有在dbxdrivers.ini檔案中的驅動程式名稱:

  aSL := TStringList.Create;
  TDBXConnectionFactory.GetConnectionFactory.GetDriverNames(aSL);

GetRegisteredDriverNames則可以取得在目前的應用程式中註冊的dbExpress驅動程式,只有在應用程式中真正註冊的驅動程式才能使用來連結相對的資料來源。

  aSL := TStringList.Create;
  TDBXConnectionFactory.GetConnectionFactory.GetRegisteredDriverNames(aSL);

例如下圖是使用上面的三個方法取得資料來源連結資訊,驅動程式以及這個範例應用程式中註冊的驅動程式,從圖中讀者就可以猜到這個範例是使用InterBase做為範例資料庫,因為目前註冊的驅動程式是InterBase,稍後我們會說明什麼是在應用程式中註冊的驅動程式。
 
http://blufiles.storage.live.com/y1p5aESZm2GbXVDVBO1WZ8h1JyZzFpgSrBgCUPtM2EIUKtQloxpoWmjdT9yhe_5gNX1
圖1-2 TDBXConnectionFactory可以存取資料來源和驅動程式資訊

在離開TDBXConnectionFactory之前,我們必須再討論一下前面我們看到的GetConnectionFactory方法。
GetConnectionFactory的功能是回傳TDBXConnectionFactory物件,但它實際回傳的是使用Singleton設計樣例的TDBXIniFileConnectionFactory物件。

GetConnectionFactory方法首先會試著載入dbxdrivers.ini和dbxconnections.ini這兩個重要的檔案內容,載入的次序是
1.    先從應用程式目前的目錄中搜尋,如果沒有,
2.    再從系統註冊器中的HKEY_CURRENT_USERSoftwareCodeGearBDS6.0dbExpress中找尋,
3.    最後再後從系統註冊器中的HKEY_LOCAL_MACHINESoftwareCodeGearBDS6.0dbExpress中找尋,

因此在您的系統註冊器中應該可以看到如下的資訊,記載了dbxdrivers.ini和dbxconnections.ini檔案的所在地:
 
http://blufiles.storage.live.com/y1pOOJgaKPCvzB1QupXDDdKnewqrCcureaT8AZuPCkBHk0ehDnL1IkN5x7IL2ac285O
圖1-3 系統註冊器中記錄了dbxdrivers.ini和dbxconnections.ini所在地

之後,GetConnectionFactory會建立唯一的一個TDBXIniFileConnectionFactory物件,再呼叫由TDBXIniFileConnectionFactory物件複載TDBXConnectionFactory的兩個抽象方法LoadDrivers和LoadConnections來分析dbxdrivers.ini和dbxconnections.ini中的資料來源資訊和驅動程式資訊:

    procedure LoadDrivers; virtual;
    procedure LoadConnections; virtual;

並且為每一個資料來源建立一個相對的TDBXProperties物件,而TDBXProperties物件中記錄的資訊就是LoadDrivers和LoadConnections方法從dbxdrivers.ini和dbxconnections.ini中分析出來的資訊。
最後當開發人員呼叫GetConnection要取得TDBXConnection物件時,TDBXConnectionFactory便會根據開發人員欲開啟的資料來源相關的TDBXProperties物件中記錄的資訊來真正的載入dbExpress驅動程式。

1-1-1 TDBXIniFileConnectionFactory類別
TDBXIniFileConnectionFactory是從TDBXConnectionFactory繼承下來的實體類別,當開發人員呼叫TDBXConnectionFactory. GetConnectionFactory方法後實際取得的物件也就是TDBXIniFileConnectionFactory物件,在目前的DBX框架實作中使用了Singleton設計樣例,也就是說整個DBX應用程式中只會建立一個TDBXIniFileConnectionFactory物件。
TDBXIniFileConnectionFactory類別最重要的就是複載TDBXConnectionFactory宣告的兩個抽象LoadDrivers和LoadConnections,這在上節中已經說明過了:

TDBXIniFileConnectionFactory = class(TDBXConnectionFactory)

  protected
    procedure LoadDrivers; override;
    procedure LoadConnections; override;
  …
end;

TDBXIniFileConnectionFactory提供了ConnectionsFile特性可以存取資料來源設定檔,也就是dbxconnections.ini的完整檔名,DriversFile特性則可以存取dbxdrivers.ini的完整檔名。例如下面的程式碼可以分別取得目前的DBX應用程式載入和使用的dbxconnections.ini和dbxdrivers.ini檔案。

(TDBXConnectionFactory.GetConnectionFactory as TDBXIniFileConnectionFactory).ConnectionsFile
(TDBXConnectionFactory.GetConnectionFactory as TDBXIniFileConnectionFactory).DriversFile

1-1-2 TDBXConnection類別
TDBXConnection類別物件是真正代表連結到資料來源的物件,TDBXConnection類別主要的功能是讓開發人員可以建立執行SQL命令的TDBXCommand物件,啟動資料庫交易,確定資料庫交易或是取消交易,存取MetaData物件以處理資料來源物件以及存取資料來源中特定資訊等功能,可以說TDBXConnection是開發人員實際執行有關資料庫處理工作中最基礎而重要的類別。
TDBXConnection提供了許多的方法和特性讓開發人員能夠進行重要的工作,下面的表格列出了其中最重要的方法/特性以及簡單的說明:

名稱

方法或特性

說明

BeginTransaction

方法

啟動資料庫交易

CommitFreeAndNil

方法

確定完成資料庫交易並釋放相關的資源

RollbackFreeAndNil

方法

取消資料庫交易並釋放相關的資源

RollbackIncompleteFreeAndNil

方法

取消未完成的資料庫交易並釋放相關的資源

CreateCommand

方法

建立一個TDBXCommand物件

GetCommandTypes

方法

取得能夠執行的命令型態

DatabaseMetaData

特性

取得TDBXDatabaseMetaData物件,TDBXDatabaseMetaData物件可以提供相關的元資料庫資訊

上述的一些方法都擁有複載(override)的原型,例如BeginTransaction方法就提供了下面的兩個原型宣告:
   
    function  BeginTransaction(Isolation: TDBXIsolation): TDBXTransaction; overload; virtual;
    function  BeginTransaction: TDBXTransaction; overload; virtual;

讓我們看看如何使用TDBXConnection物件讀者就可以瞭解如何使用上面的方法了。假設現在我們希望藉由DBX4執行下面的SQL命令:

select count(*)  from DBXTESTTABLE

我們需要使用下面的步驟來執行這個SQL命令:
1.    取得TDBXConnection物件
2.    使用TDBXConnection物件建立TDBXCommand物件,稍後的小節會討論TDBXCommand類別
3.    使用TDBXCommand物件執行SQL命令
4.    使用TDBXReader物件擷取執行結果,稍後的小節會討論TDBXReader類別

前面我們已經討論如何解決步驟1的方法,步驟2的TDBXCommand物件可以藉由呼叫TDBXConnection物件的CreateCommand方法來建立,例如下面的程式碼即是如何使用DBX4執行SQL命令的範例,在008行呼叫CreateCommand方法建立TDBXCommand物件。
接著步驟3的工作在010到012行完成,010行先設定TDBXCommand物件的CommandType特性值為執行SQL命令,011行設定TDBXCommand物件要執行的SQL命令,最後012行呼叫TDBXCommand的ExecuteQuery方法執行SQL命令。

001    procedure TForm1.執行SQL命令;
002    var
003      aCommand : TDBXCommand;
004      aReader: TDBXReader;
005    begin
006      if (Assigned(dbx4Conn)) then
007      begin
008        aCommand := dbx4Conn.CreateCommand;
009        try
010          aCommand.CommandType := TDBXCommandTypes.DbxSQL;
011          aCommand.Text := Self.edtSQL.Text;
012          aReader := aCommand.ExecuteQuery;
013          if aReader.Next then
014          begin
015            if aReader.ValueType[0].DataType = TDBXDataTypes.Int64Type then
016              Self.Caption := IntToStr(aReader.Value[0].GetInt64)
017            else
018              Self.Caption := IntToStr(aReader.Value[0].GetInt32);
019          end;
020        finally
021          FreeAndNil(aReader);
022          FreeAndNil(aCommand);
023        end;
024      end;
025    end;

TDBXCommand的ExecuteQuery方法在執行完SQL命令之後會回傳TDBXReader物件,TDBXReader物件包含了執行SQL命令的結果資料集,藉由TDBXReader的方法開發人員就可以取得執行結果。由於在這個範例中執行的結果只是一個數值,因此013行先呼叫TDBXReader的Next方法把指標指到第一筆的執行結果,再藉由存取它的Value特性值來取得最後的結果。

讀者需要注意的是,在使用完TDBXCommand和TDBXReader物件之後都需要釋放它們,這在021和022行可以看到。

再讓我們使用另外一個範例來看看如何使用TDBXConnection物件控制資料庫交易。下面的程式碼展示了如何使用DBX4在資料庫中建立資料表,這是由DBX4框架中的MetaData等相關類別提供的服務,在稍後的章節中我們會詳細的說明這些MetaData類別,在這裡讓我們先集中焦點在TDBXConnection類別。

在下面的013行中呼叫了TDBXConnection的BeginTransaction啟動資料庫交易,之後的程式碼就執行在這個資料庫交易環境,當一切正確的執行後033行呼叫TDBXConnection的CommitFreeAndNil確定資料庫交易完成,如果在015~033行中發生的錯誤就會產生例外狀況,此時035行呼叫了TDBXConnection的RollbackFreeAndNil來取消資料庫交易。
001    procedure TForm1.建立測試資料表;
002    var
003      aProvider: TDBXMetaDataProvider;
004      Transaction: TDBXTransaction;
005      MetaDataTable: TDBXMetaDataTable;
006      anIndex : TDBXMetaDataIndex;
007      aColumn : TDBXInt32Column;
008    begin
009      if (Assigned(dbx4Conn)) then
010      begin
011        aProvider := DoGetDBXMetaProvider(dbx4Conn);
012        try
013          Transaction := dbx4Conn.BeginTransaction;
014          try
015            aProvider.DropTable(TESTTABLENAME);
016            MetaDataTable := TDBXMetaDataTable.Create;
017            MetaDataTable.TableName := TESTTABLENAME;
018            aColumn := TDBXInt32Column.Create(‘序號’);
019            aColumn.Nullable := False;
020            MetaDataTable.AddColumn(aColumn);
021            MetaDataTable.AddColumn(TDBXUnicodeVarCharColumn.Create(‘研討會名稱’, 32));
022            MetaDataTable.AddColumn(TDBXUnicodeVarCharColumn.Create(‘主講人’, 10));
023            MetaDataTable.AddColumn(TDBXDateColumn.Create(‘日期’));
024            aProvider.CreateTable(MetaDataTable);
025   
026            anIndex := TDBXMetaDataIndex.Create;
027            anIndex.TableName := MetaDataTable.TableName;
028            anIndex.IndexName := ‘pidx_序號’;
029            anIndex.Unique := True;
030            anIndex.AddColumn(‘序號’);
031            aProvider.CreatePrimaryKey(anIndex);
032   
033            dbx4Conn.CommitFreeAndNil(Transaction);
034          except
035            dbx4Conn.RollbackFreeAndNil(Transaction);
036          end;
037        finally
038          anIndex.Free;
039          MetaDataTable.Free;
040          aProvider.Free;
041          dbx4Conn.RollbackIncompleteFreeAndNil(Transaction);
042        end;
043      end;
044    end;

請注意的是在041行又呼叫了TDBXConnection的RollbackIncompleteFreeAndNil方法,為什麼呢?這是因為在上面的except敘述中呼叫了TDBXConnection的RollbackFreeAndNil來取消資料庫交易,但是RollbackFreeAndNil本身也可能發生而又產生例外狀況,這有可能造成一些資源未被釋放,因此才在037行開始的finally程式區塊中呼叫RollbackIncompleteFreeAndNil來釋放任何可能未被釋放的資源,而RollbackIncompleteFreeAndNil本身不會產生例外狀況,它只是試著釋放應該被釋放的資源而已。
因此在使用TDBXConnection物件來啟動資料庫交易時,可以使用如下的設計樣例來撰寫程式碼:

aTransaction := TDBXConnection物件.BeginTransaction;
try
  //實際工作程式碼
  TDBXConnection物件.CommitFreeAndNil(aTransaction);
Except
  TDBXConnection物件. RollbackFreeAndNil(Transaction);
Finally
  TDBXConnection物件. RollbackIncompleteFreeAndNil(Transaction)
End;

5 則迴響

第3章 使用DBX4的測試框架類別(2) : 如何客製化自動產生測試資料的流程

面展示了如何使用TDbxDataGenerator自動產生測試資料,雖然DBX4框架提供了簡單的自動產生資料的能力,但在實際的開發中開發人員一定會需要產生有意義的測試資料,那麼開發人員如何能夠讓TDbxDataGenerator使用開發人員的方式來產生測試資料呢? 答案就在TDBXDataGeneratorColumn類別。
    當TDbxDataGenerator要產生測試資料時,它會使用DBX4的元資料相關類別來判斷欲產生測試資料的資料表的結構,然後根據資料表的欄位型態來建立TDBXDataGeneratorColumn衍生類別的物件來產生測試資料。也許讓我們來看看TDBXDataGeneratorColumn類別的定義,再以解釋讀者就更容易瞭解這整個流程。
下面是TDBXDataGeneratorColumn的定義,它是一個抽象類別,其中定義了許多的虛擬方法以便讓衍生類別繼承並且加以實作。

 TDBXDataGeneratorColumn = class abstract
  public
    constructor Create(const InMetaDataColumn: TDBXMetaDataColumn);
    procedure Open; virtual;
    destructor Destroy; override;
    function GetString(const Row: Int64): UnicodeString; virtual; abstract;
    function GetBoolean(const Row: Int64): Boolean; virtual;
    function GetInt8(const Row: Int64): Byte; virtual;
    function GetInt16(const Row: Int64): SmallInt; virtual;
    function GetInt32(const Row: Int64): Integer; virtual;
    function GetInt64(const Row: Int64): Int64; virtual;
    function GetDouble(const Row: Int64): Double; virtual;
    function GetSingle(const Row: Int64): Single; virtual;
    function GetBytes(const Row: Int64): TBytes; virtual;
    function GetDecimal(const Row: Int64): UnicodeString; virtual;
    function GetYear(const Row: Int64): Integer; virtual;
    function GetMonth(const Row: Int64): Integer; virtual;
    function GetDay(const Row: Int64): Integer; virtual;
    function GetHour(const Row: Int64): Integer; virtual;
    function GetMinute(const Row: Int64): Integer; virtual;
    function GetSeconds(const Row: Int64): Integer; virtual;
    function GetMilliseconds(const Row: Int64): Integer; virtual;
    procedure SetValue(const Row: Int64; const Value: TDBXWritableValue); virtual; abstract;
  protected
    procedure SetDataGenerator(const DataGenerator: TDBXCustomDataGenerator); virtual;
    function GetColumnName: UnicodeString; virtual;
    function GetDataType: Integer; virtual;
    function GetMetaDataColumn: TDBXMetaDataColumn; virtual;
  private
    function TypeNotSupported: TDBXDataGeneratorException;
  protected
    FMetaDataColumn: TDBXMetaDataColumn;
    FDataGenerator: TDBXCustomDataGenerator;
  public
    property DataGenerator: TDBXCustomDataGenerator write SetDataGenerator;
    property ColumnName: UnicodeString read GetColumnName;
    property DataType: Integer read GetDataType;
    property MetaDataColumn: TDBXMetaDataColumn read GetMetaDataColumn;
  end;

例如假設欲產生測試資料的資料表的第一個欄位型態是整數序列號,那麼TDbxDataGenerator便會建立TDBXInt32SequenceGenerator的物件來產生測試資料,由於TDBXInt32SequenceGenerator是提供產生整數序列號的測試資料,因此它只複載了TDBXDataGeneratorColumn的GetInt32虛擬方法以提供整數序列號數值,複載GetString虛擬方法以字串的型態提供整數序列號數值, TDBXInt32SequenceGenerator並不需要複載所有TDBXDataGeneratorColumn的虛擬方法。

  TDBXInt32SequenceGenerator = class(TDBXDataGeneratorColumn)
  public
    constructor Create(const MetaData: TDBXMetaDataColumn);
    procedure Open; override;
    function GetInt32(const Row: Int64): Integer; override;
    function GetString(const Row: Int64): UnicodeString; override;
    procedure SetValue(const Row: Int64; const Value: TDBXWritableValue); override;
  end;

而例如假設欲產生測試資料的資料表的第一個欄位型態是字串型態,那麼TDbxDataGenerator便會建立TDBXWideStringSequenceGenerator的物件來產生測試資料,TDBXWideStringSequenceGenerator的定義如下,它的使用方式和前面介紹的TDBXInt32SequenceGenerator是一樣的,只是它是負責提供產生字串型態的測試資料:

  TDBXWideStringSequenceGenerator = class(TDBXDataGeneratorColumn)
  public
    constructor Create(const MetaData: TDBXMetaDataColumn);
    procedure Open; override;
    function GetString(const Row: Int64): UnicodeString; override;
    procedure SetValue(const Row: Int64; const Value: TDBXWritableValue); override;
  end;

在DBX4框架中類似TDBXInt32SequenceGenerator類別以提供各種不同型態的測試資料類別超過10個以上,而且開發人員還可以自己定義/實作客製化類別,稍後我們就會討論。下面的表格說明了這些類別:

類別

說明

TDBXBooleanSequenceGenerator

提供產生布林值型態的測試資料

TDBXBlobSequenceGenerator

提供產生Blob型態的測試資料

TDBXAnsiStringSequenceGenerator

提供產生Ansi字串型態的測試資料

TDBXDateSequenceGenerator

提供產生日期型態的測試資料

TDBXDecimalSequenceGenerator

提供產生Decimal型態的測試資料

TDBXDoubleSequenceGenerator

提供產生Double型態的測試資料

TDBXInt16SequenceGenerator

提供產生16位元整數型態的測試資料

TDBXInt32SequenceGenerator

提供產生32位元整數型態的測試資料

TDBXInt64SequenceGenerator

提供產生64位元整數型態的測試資料

TDBXInt8SequenceGenerator

提供產生8位元型態的測試資料

TDBXTimeSequenceGenerator

提供產生時間型態的測試資料

TDBXTimestampSequenceGenerator

提供產生Timestamp型態的測試資料

TDBXWideStringSequenceGenerator

提供產生WideString/UnicodeString型態的測試資料

讓我們看看TDBXWideStringSequenceGenerator如何產生測試資料,下面是TDBXWideStringSequenceGenerator的GetString方法,它會在TDbxDataGenerator要為字串型態的欄位產生測試資料時呼叫:

function TDBXWideStringSequenceGenerator.GetString(const Row: Int64): UnicodeString;
var
  Value: UnicodeString;
begin
  Value := ‘W’ + IntToStr(Row);
  if FMetaDataColumn.FixedLength then
    while Length(Value) < FMetaDataColumn.Precision do
      Value := Value + ‘ ‘;
  Result := Value;
end;

從上面的實作程式碼可以知道,TDBXWideStringSequenceGenerator.GetString在產生測試資料時是以’W’字元為起頭並且加入正在產生測試資料的列行數的數值,例如為第一筆資料產生的是W1,第2筆就是W2等以此類推,這也解釋了為什麼圖3-2中『研討會名稱』和『主講人』欄位的測試資料就是這樣的內容。
瞭解了上面討論的原理之後,那麼我們如何讓TDbxDataGenerator產生我們需要的測試資料而不是內定的簡單而無意義的測試資料呢? 答案很簡單,我們只需要進行下面的兩項工作:

  1. 實作從TDBXDataGeneratorColumn衍生的類別,在這個衍生的類別中使用程式碼或是其他方式(例如從檔案中載入測試資料)來產生測試資料
  2. 通知TDbxDataGenerator建立我們實作的衍生的類別來為測試資料表的欄位來產生測試資料

首先讓我們看看如何完成上面的第二個步驟,這個工作很簡單,我們只需要建立客製化TDBXDataGeneratorColumn衍生類別物件,並且加入到TDbxDataGenerator物件之中即可。
例如在下面的程式碼中,我們首先在047行中藉由元資料類別取得測試資料表的每一個欄位物件,然後在049到067行中一一的判斷每一個欄位物件的資料型態,接著就建立相對應的TDBXDataGeneratorColumn客製化衍生類別物件,再呼叫TDbxDataGenerator的AddColumn方法把它加入到TDbxDataGenerator物件之中。

001    procedure TForm5.加入欄位物件(aProvider : TDBXMetaDataProvider; DataGenerator: TDbxDataGenerator);
002   
003      function 建立整數欄位(cName : string) : TDBXInt32SequenceGenerator;
004      var
005        col : TDBXInt32Column;
006      begin
007        col := TDBXInt32Column.Create(cName);
008        try
009          Result := TDBXInt32SequenceGenerator.Create(col);
010        finally
011          FreeAndNil(col);
012        end;
013      end;
014   
015      function 建立字串欄位(cName : string; iLength : Integer) : TDBXWideStringSequenceGenerator;
016      var
017        col : TDBXUnicodeVarCharColumn;
018      begin
019        col := TDBXUnicodeVarCharColumn.Create(cName, iLength);
020        try
021          if (col.ColumnName = ‘主講人’) then
022            Result := T主講人產生器.Create(col)
023          else
024            if (col.ColumnName = ‘研討會名稱’) then
025              Result := T研討會產生器.Create(col)
026            else
027              Result := TDBXWideStringSequenceGenerator.Create(col);
028        finally
029          FreeAndNil(col);
030        end;
031      end;
032   
033      function 建立日期欄位(cName : string) : TDBXDateSequenceGenerator;
034      var
035        col : TDBXDateColumn;
036      begin
037        col := TDBXDateColumn.Create(cName);
038        try
039          Result :=TDBXDateSequenceGenerator.Create(col);
040        finally
041          FreeAndNil(col);
042        end;
043      end;
044    var
045      cols : TDBXColumnsTableStorage;
046    begin
047      cols := 取得欄位物件(aProvider, TESTTABLENAME);
048   
049      while (cols.InBounds) do
050      begin
051        case cols.DbxDataType of
052          TDBXDataTypes.UnknownType, TDBXDataTypes.Int32Type :
053          begin
054            DataGenerator.AddColumn(建立整數欄位(cols.ColumnName));
055          end;
056          TDBXDataTypes.WideStringType :
057          begin
058            DataGenerator.AddColumn(建立字串欄位(cols.ColumnName, cols.Precision));
059          end;
060          TDBXDataTypes.DateType :
061          begin
062            DataGenerator.AddColumn(建立日期欄位(cols.ColumnName));
063          end;
064        end;
065        cols.Next;
066      end;
067    end;

那麼步驟1如何完成? 也很簡單,我們只需要定義從TDBXDataGeneratorColumn或是它的衍生類別繼承下來的客製化類別,再於其中撰寫如何產生測試資料的程式碼即可。
例如下面就是上面程式碼使用的『T研討會產生器』類別,這個類別負責在測試資料表的『研討會名稱』欄位中產生測試資料:

001    unit u研討會產生器;
002   
003    interface
004    uses
005      DBXCommon,
006      DBXCommonTable,
007      DBXMetaDataProvider,
008      SysUtils,
009      DBXCustomDataGenerator;
010   
011    type
012      T研討會產生器 = class(TDBXWideStringSequenceGenerator)
013      public
014        constructor Create(const MetaData: TDBXMetaDataColumn);
015        procedure Open; override;
016        function GetString(const Row: Int64): UnicodeString; override;
017        procedure SetValue(const Row: Int64; const Value: TDBXWritableValue); override;
018      private
019        dataArray : array[0..5] of UnicodeString;
020        iPos : Integer;
021        procedure InitailizeData;
022      end;
023   
024    implementation
025   
026    { TDBXMyWideStringSequenceGenerator }
027   
028    constructor T研討會產生器.Create(
029      const MetaData: TDBXMetaDataColumn);
030    begin
031      inherited Create(MetaData);
032      InitailizeData;
033    end;
034   
035    function T研討會產生器.GetString(
036      const Row: Int64): UnicodeString;
037    begin
038      Result := dataArray[iPos mod 6];
039      Inc(iPos);
040    end;
041   
042    procedure T研討會產生器.InitailizeData;
043    begin
044      dataArray[0] := ‘Delphi 2009產品技術發表會’;
045      dataArray[1] := ‘C++Builder 2009產品技術發表會’;
046      dataArray[2] := ‘Delphi 2009 Unicode程式設計’;
047      dataArray[3] := ‘Delphi 2009 DataSnap程式設計’;
048      dataArray[4] := ‘C++Builder 2009 Unicode程式設計’;
049      dataArray[5] := ‘C++Builder 2009 DataSnap程式設計’;
050      iPos := 0;
051    end;
052   
053    procedure T研討會產生器.Open;
054    begin
055      inherited open;
056    end;
057   
058    procedure T研討會產生器.SetValue(const Row: Int64;
059      const Value: TDBXWritableValue);
060    begin
061      Value.SetWideString(GetString(Row));
062    end;
063   
end.

正是由於我們提供了客製化的『T研討會產生器』類別,因此TDbxDataGenerator才在圖3-3中產生了客製化的測試資料,如何?整個客製化的流程很簡單吧。

Ok,希望現在各位就瞭解如何使用和整合DBX4框架來自動測試任何的測試資料了。

3 則迴響