亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > Delphi > 正文

用DELPHI的RTTI實現數據集的簡單對象化

2019-11-18 18:04:09
字體:
來源:轉載
供稿:網友
 在《強大的DELPHI RTTI--兼談需要了解多種開發語言》一文中,我說了一下我用DELPHI的RTTI實現了數據集的簡單對象化。本文將詳細介紹一下我的實現方法。

    首先從一個簡單的例子說起:假設有一個ADODataSet控件,連接羅斯文數據庫,SQL為:

select * from Employee

    現在要把它的內容中EmployeeID, FirstName, LastName,BirthDate四個字段顯示到ListView里。傳統的代碼如下:

    With ADODataSet1 Do    Begin        Open;        While Not Eof Do        Begin            With ListView1.Add Do            Begin                Caption := IntToStr( FieldByName( 'EmployeeID' ).AsInteger );                SubItems.Add( FieldByName( 'FirstName' ).AsString );                SubItems.Add( FieldByName( 'LastName' ).AsString );                SubItems.Add( FormatDateTime( FieldByName( 'BirthDate' ).AsDateTime ) );            End;            Next;        End;        Close;    End;

    這里主要存在幾個方面的問題:

    1、首先是有很多代碼非常冗長。比如FieldByName和AsXXX等,特別是AsXXX,必須時時記得每個字段是什么類型的,很容易搞錯。而且有些不兼容的類型如果不能自動轉換的話,要到運行時才能發現錯誤。

    2、需要自己在循環里處理當前記錄的移動。如上面的Next,否則一旦忘記就會發生死循環,雖然這種問題很容易發現并處理,但程序員不應該被這樣的小細節所糾纏。

    3、最主要的是字段名通過String參數傳遞,如果寫錯的話,要到運行時才會發現,增加了潛在的BUG可能性,特別是如果測試沒有完全覆蓋所有的FieldByName,很可能使這樣的問題拖到客戶那邊才會出現。而這種寫錯字段名的情況是很容易發生的,特別是當程序使用了多個表時,還容易將不同表的字段名搞混。

    在這個由OO統治的時代里,碰到與數據集有關的操作時,我們還是不得不常常陷入上面說的這些關系數據庫方面的細節問題中。當然現在也有擺脫它們的辦法,那就是O/R mapping,但是O/R mapping畢竟與傳統的開發方式差別太大,特別是對于一些小的應用來說,沒必要這么夸張,在這種情況下,我們需要的只是一個簡單的數據集對象化方案。

    在java及其它動態語言的啟發下,我想到了用DELPHI強大的RTTI來實現這個簡單的數據集對象化方案。下面是實現與傳統代碼同樣功能的數據集對象化應用代碼:

Type    TDSPEmployee = class(TMDataSetPRoxy)    published        Property EmployeeID : Integer Index 0 Read GetInteger Write SetInteger;        Property FirstName  : String  Index 1 Read GetString  Write SetString;        Property LastName   : String  Index 2 Read GetString  Write SetString;        Property BirthDate  : Variant Index 3 Read GetVariant Write SetVariant;    end;procedure TForm1.ListClick(Sender: TObject);Var    emp : TDSPEmployee;begin    emp := TDSPEmployee.Create( ADODataSet1 );    Try        While ( emp.ForEach ) Do        With ListView1.Items.Add Do        Begin            Caption := IntToStr( emp.EmployeeID );            SubItems.Add( emp.FirstName );            SubItems.Add( emp.LastName );            SubItems.Add( FormatDateTime( 'yyyy-mm-dd', TDateTime( emp.BirthDate ) ) );        End;    Finally        emp.Free;    End;end;

    用法很簡單。最主要的是要先定義一個代理類,其中以Published的屬性來定義所有的字段,包括其類型,之后就可以以對象的方式來操作數據集了。這個代理類是從TMDataSetProxy派生來的,其中用RTTI實現了從屬性操作到字段操作的映射,使用時只要簡單地Uses一下相應的單元即可。關于這個類的實現單元將在下面詳細說明。

    表面上看多了一個定義數據集的代理類,好像多了一些代碼,但這是一件一勞永逸的事,特別是當程序中需要多次重用同樣結構的數據集的情況下,將會使代碼量大大減少。更何況這個代理類的定義非常簡單,只是根據字段名和字段類型定義一系列的屬性罷了,不用任何實現代碼。其中用到的屬性存取函數 GetXXX/SetXXX都在基類TMDataSetProxy里實現了。

    現在再來看那段與原代碼對應的循環:

    1、FieldByName和AsXXX都不需要了,變成了對代理類的屬性操作,而且每個字段對應的屬性的類型在前面已經定義好了,不用再每次用到時來考慮一下它是什么類型的。如果用錯了類型,在編譯時就會報錯。

    2、用一個ForEach來進行記錄遍歷,不用再擔心忘記Next造成的死循環了。

    3、最大的好處是字段名變成了屬性,這樣就可以享受到編譯時字段名校驗的好處了,除非是定義代理類時就把字段名寫錯,否則都能在編譯時發現。

    現在開始討論TMDataSetProxy。其實現的代碼如下:

(******************************************************************用RTTI實現的數據集代理,可以簡單地將數據集對象化。Copyright (c) 2005 by Mental Studio.Author : 猛禽Date   : Jan.28-05******************************************************************)unit MDSPComm;interfaceUses    Classes, DB, TypInfo;Type    TMPropList = class(TObject)    private        FPropCount : Integer;        FPropList  : PPropList;    protected        Function GetPropName( aIndex : Integer ) : ShortString;        function GetProp(aIndex: Integer): PPropInfo;    public      constructor Create( aObj : TPersistent );      destructor  Destroy; override;      property PropCount : Integer Read FPropCount;      property PropNames[aIndex : Integer] : ShortString Read GetPropName;      property Props[aIndex : Integer] : PPropInfo Read GetProp;    End;    TMDataSetProxy = class(TPersistent)    private        FDataSet  : TDataSet;        FPropList : TMPropList;        FLooping  : Boolean;    protected        Procedure BeginEdit;        Procedure EndEdit;        Function  GetInteger( aIndex : Integer ) : Integer; Virtual;        Function  GetFloat(   aIndex : Integer ) : Double;  Virtual;        Function  GetString(  aIndex : Integer ) : String;  Virtual;        Function  GetVariant( aIndex : Integer ) : Variant; Virtual;        Procedure SetInteger( aIndex : Integer; aValue : Integer ); Virtual;        Procedure SetFloat(   aIndex : Integer; aValue : Double  ); Virtual;        Procedure SetString(  aIndex : Integer; aValue : String  ); Virtual;        Procedure SetVariant( aIndex : Integer; aValue : Variant ); Virtual;    public      constructor Create( aDataSet : TDataSet );      destructor  Destroy; override;      Procedure AfterConstruction; Override;      function  ForEach : Boolean;      Property DataSet : TDataSet Read FDataSet;    end;implementation{ TMPropList }constructor TMPropList.Create(aObj: TPersistent);begin    FPropCount := GetTypeData(aObj.ClassInfo)^.PropCount;    FPropList  := Nil;    if FPropCount > 0 then    begin        GetMem(FPropList, FPropCount * SizeOf(Pointer));        GetPropInfos(aObj.ClassInfo, FPropList);    end;end;destructor TMPropList.Destroy;begin    If Assigned( FPropList ) Then        FreeMem( FPropList );    inherited;end;function TMPropList.GetProp(aIndex: Integer): PPropInfo;begin    Result := Nil;    If ( Assigned( FPropList ) ) Then        Result := FPropList[aIndex];end;function TMPropList.GetPropName(aIndex: Integer): ShortString;begin    Result := GetProp( aIndex )^.Name;end;{ TMRefDataSet }constructor TMDataSetProxy.Create(aDataSet: TDataSet);begin    Inherited Create;    FDataSet := aDataSet;    FDataSet.Open;    FLooping := false;end;destructor TMDataSetProxy.Destroy;begin    FPropList.Free;    If Assigned( FDataSet ) Then        FDataSet.Close;    inherited;end;procedure TMDataSetProxy.AfterConstruction;begin    inherited;    FPropList := TMPropList.Create( Self );end;procedure TMDataSetProxy.BeginEdit;begin    If ( FDataSet.State <> dsEdit ) AND ( FDataSet.State <> dsInsert ) Then        FDataSet.Edit;end;procedure TMDataSetProxy.EndEdit;begin    If ( FDataSet.State = dsEdit ) OR ( FDataSet.State = dsInsert ) Then        FDataSet.Post;end;function TMDataSetProxy.GetInteger(aIndex: Integer): Integer;begin    Result := FDataSet.FieldByName( FPropList.PropNames[aIndex] ).AsInteger;end;function TMDataSetProxy.GetFloat(aIndex: Integer): Double;begin    Result := FDataSet.FieldByName( FPropList.PropNames[aIndex] ).AsFloat;end;function TMDataSetProxy.GetString(aIndex: Integer): String;begin    Result := FDataSet.FieldByName( FPropList.PropNames[aIndex] ).AsString;end;function TMDataSetProxy.GetVariant(aIndex: Integer): Variant;begin    Result := FDataSet.FieldByName( FPropList.PropNames[aIndex] ).Value;end;procedure TMDataSetProxy.SetInteger(aIndex, aValue: Integer);begin    BeginEdit;    FDataSet.FieldByName( FPropList.PropNames[aIndex] ).AsInteger := aValue;end;procedure TMDataSetProxy.SetFloat(aIndex: Integer; aValue: Double);begin    BeginEdit;    FDataSet.FieldByName( FPropList.PropNames[aIndex] ).AsFloat := aValue;end;procedure TMDataSetProxy.SetString(aIndex: Integer; aValue: String);begin    BeginEdit;    FDataSet.FieldByName( FPropList.PropNames[aIndex] ).AsString := aValue;end;procedure TMDataSetProxy.SetVariant(aIndex: Integer; aValue: Variant);begin    BeginEdit;    FDataSet.FieldByName( FPropList.PropNames[aIndex] ).Value := aValue;end;function TMDataSetProxy.ForEach: Boolean;begin    Result := Not FDataSet.Eof;    If FLooping Then    Begin        EndEdit;        FDataSet.Next;        Result := Not FDataSet.Eof;        If Not Result Then        Begin            FDataSet.First;            FLooping := false;        End;    End    Else If Result Then        FLooping := true;end;end.

    其中TMPropList類是一個對RTTI的屬性操作部分功能的封裝。其功能就是利用DELPHI在TypInfo單元中定義的一些 RTTI函數,實現為一個TPersistent的派生類維護其Published的屬性列表信息。代理類就通過這個屬性列表來取得屬性名,并最終通過這個屬性名與數據集中的相應字段進行操作。

    TMDataSetProxy就是數據集代理類的基類。其最主要的部分就是在AfterConstruction里創建屬性列表。

    屬性的操作在這里只實現了Integer, Double/Float, String, Variant這四種數據類型。如果需要,可以自己在此基礎上派生自己的代理基類實現其它數據類型的實現,而且這幾個已經實現的類型的屬性操作實現都被定義為虛函數,也可以在派生基類里用自己的實現取代它。不過對于不是很常用的類型,建議可以定義實際的代理類時再實現。比如前面的例子中,假設 TDateTime不是一個常用的類型,可以這樣做:

    TDSPEmployee = class(TMDataSetProxy)    protected        function  GetDateTime(const Index: Integer): TDateTime;        procedure SetDateTime(const Index: Integer; const Value: TDateTime);    published        Property EmployeeID : Integer Index 0 Read GetInteger Write SetInteger;        Property FirstName  : String  Index 1 Read GetString  Write SetString;        Property LastName   : String  Index 2 Read GetString  Write SetString;        Property BirthDate  : TDateTime Index 3 Read GetDateTime Write SetDateTime;    end;{ TDSPEmployee }function TDSPEmployee.GetDateTime(const Index: Integer): TDateTime;begin    Result := TDateTime( GetVariant( Index ) );end;procedure TDSPEmployee.SetDateTime(const Index: Integer;  const Value: TDateTime);begin    SetVariant( Index, Value );end;

    這樣下面就可以直接把BirthDate當作TDateTime類型使用了。

    另外,利用這一點,還可以為一些自定義的特別的數據類型提供統一的操作。

    另外,在所有的SetXXX之前都調用了一下BeginEdit,以避免忘記使用DataSet.Edit導致的運行時錯誤。

    ForEach被實現成可以重復使用的,在每次ForEach完成一次遍歷后,將當前記錄移動最第一條記錄上以備下次的循環。另外,在Next之前調用了EndEdit,自動提交所作的修改。

    這個數據集對象化方案是一種很簡單的方案,現在存在的最大的一個問題就是屬性的Index參數必須嚴格按照屬性在定義時的順序,否則就會取錯字段。這是因為DELPHI畢竟還是一種原生開發語言,調用GetXXX/SetXXX時區別同類型的不同屬性的唯一途徑就是通過Index,而這個 Index參數是在編譯時就確定地傳給函數了,并沒有一個動態的表來記錄,所以只能采用現在這樣的方法來將就。


上一篇:用DELPHI的RTTI實現對象的XML持久化

下一篇:防止全局hook入侵Delphi版,2000以上系統適用(part1)

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
學習交流
熱門圖片

新聞熱點

疑難解答

圖片精選

網友關注

亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
欧美亚洲另类视频| 久久久国产在线视频| 日本国产精品视频| 精品国产欧美成人夜夜嗨| 国产在线拍偷自揄拍精品| 亚洲国产精品中文| 日韩视频永久免费观看| 国产成人涩涩涩视频在线观看| 欧美日韩国产综合视频在线观看中文| 久久精品国产91精品亚洲| 国产精品久久久久久久天堂| 亚洲欧美国产精品专区久久| 国产精品美女免费看| 成人国产在线视频| 亚洲色图综合网| 91精品国产精品| 亚洲自拍偷拍视频| 亚洲国产黄色片| 欧美午夜宅男影院在线观看| 日韩中文字幕网| 成人a级免费视频| 51久久精品夜色国产麻豆| 亚洲精品资源美女情侣酒店| 亚洲国产精品成人va在线观看| 欧美国产亚洲视频| 色综合天天狠天天透天天伊人| 欧美日本中文字幕| 伦伦影院午夜日韩欧美限制| 亚洲国产精品久久91精品| 亚洲97在线观看| 国产91色在线|| 久久亚洲欧美日韩精品专区| 欧美大片免费观看| 欧美做爰性生交视频| 欧美色视频日本高清在线观看| 日本乱人伦a精品| 亚洲精品国产电影| 欧美成人性生活| 精品国产91久久久久久| 热久久这里只有| 亚洲国产另类 国产精品国产免费| 欧美激情精品久久久久久| 国产一区二区三区丝袜| 亚洲国产精品va在线看黑人| 亚洲精品资源在线| 国产一区二区三区视频免费| 97人洗澡人人免费公开视频碰碰碰| 亚洲性视频网站| 亚洲成人激情在线观看| 成人免费黄色网| 伊人一区二区三区久久精品| 国产成人精品在线视频| 日韩电影大片中文字幕| 精品国产区一区二区三区在线观看| 亚洲理论片在线观看| 日韩精品极品视频| 亚洲国产成人av在线| 欧美日韩在线视频观看| 亚洲国产成人一区| 欧美日韩亚洲天堂| 欧美与黑人午夜性猛交久久久| 成人激情视频小说免费下载| 国产日产欧美a一级在线| 日韩精品高清在线观看| 国产91在线视频| 亚洲理论在线a中文字幕| 乱亲女秽乱长久久久| 日韩精品久久久久久久玫瑰园| 亚洲精品美女免费| 最近2019中文字幕在线高清| 国产免费一区二区三区在线观看| 欧美亚洲另类在线| 日韩高清免费观看| 亚洲人成电影在线播放| 亚洲国产精品久久精品怡红院| 最新国产精品拍自在线播放| 久久精品国产69国产精品亚洲| 欧美激情综合色综合啪啪五月| 91牛牛免费视频| 成人信息集中地欧美| 国产精品爱啪在线线免费观看| 国内精品一区二区三区四区| 欧美成人一区二区三区电影| 久久精品久久久久久国产 免费| 欧日韩在线观看| 97碰在线观看| 久久久久久久久久亚洲| 欧美激情一区二区三区在线视频观看| 中文国产成人精品久久一| 精品五月天久久| 一区二区三区动漫| 成人高清视频观看www| 亚洲国产成人精品女人久久久| 国产不卡一区二区在线播放| 久久精品国产久精国产一老狼| 亚洲精品网站在线播放gif| 97在线观看视频| 亚洲第一区在线观看| 91久久久精品| 久青草国产97香蕉在线视频| 粉嫩老牛aⅴ一区二区三区| 国产成人精品电影久久久| 国产97在线观看| 欧美猛少妇色xxxxx| 久久久久国产视频| 亚洲精品国产福利| 亚洲精品成人免费| 亚洲福利影片在线| 亚洲欧美在线磁力| 欧美激情奇米色| 91chinesevideo永久地址| 亚洲伊人成综合成人网| 欧美大片网站在线观看| 久久777国产线看观看精品| 日本国产精品视频| 欧美www视频在线观看| 欧美怡春院一区二区三区| 九九综合九九综合| 亚洲国产精品美女| 日韩在线中文字幕| 精品久久久一区二区| 久久精品亚洲精品| 久久精品久久精品亚洲人| 国产精品久久久久久久久久三级| 欧美视频中文字幕在线| 日韩av在线免费观看一区| 中文字幕亚洲天堂| 欧美激情精品久久久久久变态| 国产成人精品av在线| 国产精品久久久久久久久久ktv| 亚洲欧洲偷拍精品| 亚洲午夜av电影| 成人国产精品色哟哟| 久久久久99精品久久久久| 亚洲成人久久久| 亚洲人成绝费网站色www| 在线中文字幕日韩| 91精品久久久久久久久久久久久久| 在线精品视频视频中文字幕| 久久香蕉国产线看观看av| 中文字幕日韩高清| 欧美日韩综合视频网址| 中文字幕亚洲欧美日韩2019| 在线中文字幕日韩| 久久99热精品这里久久精品| 欧美成人精品在线观看| 亚洲天堂视频在线观看| 亚洲精品久久久久| 欧美福利在线观看| 国产成人精品av| 伊人一区二区三区久久精品| 日韩三级成人av网| 亚洲午夜av电影| 亚洲韩国日本中文字幕| 久久久久九九九九| 久久精品色欧美aⅴ一区二区| 国产成人一区二区三区小说| 欧美视频在线观看免费网址| 亚洲性视频网站| 亚洲欧美综合另类中字| 欧美午夜片在线免费观看| 少妇精69xxtheporn| 亚洲欧美国产精品va在线观看| 国产精品三级久久久久久电影|