程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> Delphi >> 用DELPHI的RTTI實現對象的XML持久化

用DELPHI的RTTI實現對象的XML持久化

編輯:Delphi
去年我花了很多時間嘗試用DELPHI進行基於XML的WEB應用開發。起初的設想是很美好的,但結果做出來的東西很簡陋。一部分原因就在於XML到Object之間的數據綁定實現太麻煩(另一部分是因為對XSLT不熟,學習它花了很多時間)。

      之前我一直是用DELPHI提供的XML Data binding來做的,基本做法是:先用工具(如XMLSPY)做好一個XML Schema(XSD),然後用XML Data binding生成DELPHI的接口和類。當然,一旦生成好就很方便了,在程序裡我只要操作這個接口就好了,其中各個Field都會被變成屬性,並且類型也都如我在XSD中的定義。但問題在於程序在開發過程中,總是會有一些變化的,在這種情況下,我就不得不同時開著XMLSPY修改XSD,然後重新用 XML Data binding的Wizard跑一遍,非常的麻煩。

      所以當我想到數據集的對象化後,立即想到也可以用RTTI來實現Object的XML持久化--其實DELPHI6開始的SOAP實現就是用RTTI來實現Object到SOAP數據(就是XML)的轉換的。顯然我已經是非常的後知後覺了,當我在《強大的DELPHI RTTI--兼談需要了解多種開發語言》一文中說到我的打算時,朋友Lex CHow回復我說他在大約一年前就做過了這方面的工作,我當即跟他要來了他的源碼。lexlib是他寫的是一個有很多功能的庫,看上去結構有點像.net 的基本類庫(當然沒那麼大^O^),Object的XML持久化只是其中的很小的一部分。因為我只需要這一部分,就沒必要用這整個一個庫這麼麻煩,於是參考了lexlib並結合我在《用DELPHI的RTTI實現數據集的簡單對象化》中已經實現的部分,做了一個簡單的實現:

    TMXMLPersistent = class(TObject)
    public
        class Procedure LoadObjFromXML( aNode : IXMLNode; aObj : TPersistent );
        class Procedure SaveObjToXML(   aNode : IXMLNode; aObj : TPersistent );
    end;

const
    DefaultFilter : TTypeKinds = [tkInteger, tkChar, tkEnumeration,
        tkFloat, tkString, tkSet, tkWChar, tkLString, tkWString, tkInt64];

{ TMXMLPersistent }

class procedure TMXMLPersistent.LoadObjFromXML(aNode: IXMLNode;
  aObj: TPersistent);
Var
    i : Integer;
    pList : TMPropList;
    pInfo : PPropInfo;
    tmpObj: TObject;
begin
    If ( aObj Is TMDataSetProxy ) Then
        ( aObj As TMDataSetProxy ).LoadFromXML( aNode )
    Else
    Begin
        pList := TMPropList.Create( aObj );
        Try
            For i := 0 To pList.PropCount - 1 Do
            Begin
                pInfo := pList.Props[i];
                If ( pInfo^.PropType^.Kind = tkClass ) Then
                Begin
                    tmpObj := TObject( Integer( GetPropValue( aObj, pInfo^.Name ) ) );
                    If ( Assigned( tmpObj ) AND ( tmpObj Is TPersistent ) ) Then
                        LoadObjFromXML( aNode.ChildNodes[WideString(pInfo^.Name)],
                            tmpObj As TPersistent );
                End
                Else If ( pInfo^.PropType^.Kind In DefaultFilter ) Then
                    SetPropValue( aObj, pInfo^.Name,
                        String( aNode.ChildNodes[WideString( pInfo^.Name )].Text ) );
            End;
        Finally
            pList.Free;
        End;
    End;
end;

class procedure TMXMLPersistent.SaveObjToXML(aNode: IXMLNode;
  aObj: TPersistent);
Var
    i : Integer;
    pList : TMPropList;
    pInfo : PPropInfo;
    tmpObj: TObject;
begin
    If ( aObj Is TMDataSetProxy ) Then
        ( aObj As TMDataSetProxy ).SaveToXML( aNode )
    Else
    Begin
        pList := TMPropList.Create( aObj );
        Try
            For i := 0 To pList.PropCount - 1 Do
            Begin
                pInfo := pList.Props[i];
                If ( pInfo^.PropType^.Kind = tkClass ) Then
                Begin
                    tmpObj := TObject( Integer( GetPropValue( aObj, pInfo^.Name ) ) );
                    If ( Assigned( tmpObj ) AND ( tmpObj Is TPersistent ) ) Then
                        SaveObjToXML( aNode.AddChild( WideString( pInfo^.Name ) ),
                            tmpObj As TPersistent );
                End
                Else If ( pInfo^.PropType^.Kind In DefaultFilter ) Then
                    aNode.AddChild( WideString( pInfo^.Name ) ).Text :=
                        GetPropValue( aObj, pInfo^.Name );
            End;
        Finally
            pList.Free;
        End;
    End;
end;

      這個實現應該說是很簡單的。主要是三個部分(Load和Save的結構是相似的):

      一是對TMDataSetProxy作特別處理,委托給這個類自己去處理它的實現,因為它與一般的類不同,需要通過ForEach遍歷全部記錄,這其實就是同時實現數據集的XML持久化。

      二是對Class作遞歸處理,當然只支持從TPersistent派生的class。

      三是一般的Field簡單地轉成String保存,其中借鑒了lexlib的Filter,只處理那些能簡單地轉成String的數據類型,過濾掉那些可能造成轉換出錯的類型。

      上面的代碼中用到的TMPropList見《用DELPHI的RTTI實現數據集的簡單對象化》中的實現。
  

      下面是用TMDataSetProxy實現的數據集的XML持久化。免去了需要通過TClientDataSet進行的麻煩,並且采用的是用Node記錄字段的方式,.net也是采用這樣的方式,與TClientDataSet所用的用Attribute記錄字段的方式不同。雖然這樣生成的 XML文件將會略大一些,但是好處也是顯而易見的,特別是我是准備用在Web應用中的,用Node方式記錄的XML,在用XSLT時會方便很多。

procedure TMDataSetProxy.LoadFromXML(aNode: IXMLNode);
Var
    i, j : Integer;
    pInfo : PPropInfo;
    pRow  : IXMLNode;
begin
    For j := 0 To aNode.ChildNodes.Count - 1 Do
    Begin
        FDataSet.Append;
        pRow := aNode.ChildNodes[j];
        For i := 0 To FPropList.PropCount - 1 Do
        Begin
            pInfo := FPropList.Props[i];
            If ( pInfo^.PropType^.Kind In DefaultFilter ) Then
                SetVariant( i,
                    String( pRow.ChildNodes[WideString( pInfo^.Name )].Text ) );
        End;
        EndEdit;
    End;
    FDataSet.First;
end;

procedure TMDataSetProxy.SaveToXML(aNode: IXMLNode);
Var
    i : Integer;
    pInfo : PPropInfo;
    pRow  : IXMLNode;
begin
    While ForEach Do
    Begin
        pRow := aNode.AddChild( 'Row' );
        For i := 0 To FPropList.PropCount - 1 Do
        Begin
            pInfo := FPropList.Props[i];
            If ( pInfo^.PropType^.Kind In DefaultFilter ) Then
                pRow.AddChild( WideString( pInfo^.Name ) ).Text
                    := GetVariant( i );
        End;
    End;
end;

      下面是一個簡單的DEMO,其中包括了對數據集的XML持久化。注意Load的時候Employee成員連接的是ADODataSet2,它連接到一個包含了這幾個字段的表,各字段類型與Employee表相同,但內容為空,並且去掉了EmployeeID的Identity。Load完成後,Employee表中這幾個字段的內容將被復制到此表中。

    TDemoCompany = class( TPersistent )
    private
        FEmployee : TDSPEmployee;
        FCompany  : String;
        FCode     : Integer;
    published
        property Employee : TDSPEmployee Read FEmployee Write FEmployee;
        property Company  : String       Read FCompany  Write FCompany;
        Property Code     : Integer      Read FCode     Write FCode;
    End;

procedure TForm1.SaveClick(Sender: TObject);
Var
    demo : TDemoCompany;
begin
    demo := TDemoCompany.Create;
    demo.Employee := TDSPEmployee.Create( ADODataSet1 );
    demo.Company  := 'Demo company';
    demo.Code     := 987654;
    Try
        XMLDocument1.Active := true;
        TMXMLPersistent.SaveObjToXML( XMLDocument1.AddChild( 'Demo' ), demo );
        XMLDocument1.SaveToFile( 'temp.xml' );
        XMLDocument1.Active := false;
    Finally
        demo.Employee.Free;
        demo.Employee := Nil;
        demo.Free;
    End;
end;

procedure TForm1.LoadClick(Sender: TObject);
Var
    demo : TDemoCompany;
begin
    demo := TDemoCompany.Create;
    demo.Employee := TDSPEmployee.Create( ADODataSet2 );
    Try
        XMLDocument1.Active := true;
        XMLDocument1.LoadFromFile( 'temp.xml' );
        TMXMLPersistent.LoadObjFromXML( XMLDocument1.ChildNodes.Last, demo );
        XMLDocument1.Active := false;
        Edit1.Text := demo.Company;
        Edit2.Text := IntToStr( demo.Code );
        While ( demo.Employee.ForEach ) Do
        With ListView1.Items.Add Do
        Begin
            Caption := IntToStr( demo.Employee.EmployeeID );
            SubItems.Add( demo.Employee.FirstName );
            SubItems.Add( demo.Employee.LastName );
            SubItems.Add( FormatDateTime( 'yyyy-mm-dd', demo.Employee.BirthDate ) );
        End;
    Finally
        demo.Employee.Free;
        demo.Employee := Nil;
        demo.Free;
    End;
end;

      終於可以告別那個麻煩的XML Data binding了,並且以後也不用寫XSD了--雖然有好用的工具,但能省點事終歸是好的。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved