首 页 网络编程
网页制作 图形图象 操作系统 冲浪宝典
软件教学 认证考试

网络安全 网络办公 行业资讯 评测对比
您当前位置:站长天空 -> 网络编程-> Visual Basic教程
用delphi的rtti实现数据集的简单对象化_delphi教程
作者:网友供稿 点击:0
推荐
西部数码-全国虚拟主机10强!20余项虚拟主机管理功能,全国领先!第6代双线路虚拟主机,南北访问畅通无阻!可在线rar解压,自动数据恢复设置虚拟目录等.免费赠送访问统计,企业邮局.Cn域名注册10元/年,自助建站480元起,免费试用7天,满意再付款!P4主机租用799元/月.月付免压金
站内搜索
文章页数:[1] 
 在《强大的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;

interface

Uses
    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参数是在编译时就确定地传给函数了,并没有一个动态的表来记录,所以只能采用现在这样的方法来将就。


文章整理:站长天空 网址:http://www.z6688.com/
以上信息与文章正文是不可分割的一部分,如果您要转载本文章,请保留以上信息,谢谢!

文章页数:[1] 


放大字体显示 缩小字体显示 打印文章 推荐给朋友
热门文章
·ASP.net中动态加载控件时一些问题的总结-ASP教程,ASP应用
·C#读写注册表-.NET教程,C#语言
·Visual Basic .NET中的异常处理简介(下)-.NET教程,VB.Net语言
·C#:文件的按行读/写及文件目录对话框的使用-.NET教程,C#语言
·ADO.Net:使用DataReader向数据库中插入数据-ASP教程,数据库相关
·列一张网恋赔偿清单(爆笑)
·如何用Photoshop画服装款式图-网页设计,Photoshop
·.NET下使用DataAdapter保存数据时,如何生成command语句及使用事务-.NET教程,数据库应用
·新型dc/dc电源控制芯片dpa426的应用
·ASP.NET 2.0 - Enter Key - Default Submit Button-.NET教程,Asp.Net开发
最新文章
·个人站长的网络赚钱两条新出路_网赚技巧
·adsense帐户最佳化纵深谈-adsense资深专员_网赚技巧
·google adsense容易被k的可能性列表_网赚技巧
·如何让程序被站长接受和产生利润_站长访谈
·马云,即成的中国互联网第4代霸主_站长访谈
·google关键词广告创建的十二招_google推广
·如何使google更快速收录你的新站_google推广
·几个颇有创意的网站推广方法_站长心得
·网络编辑:标题,如何让网民一见钟情(2)_网络编辑
·网站建设基础seo搜索引擎优化_seo网站优化
相关主题
  • 用delphi实现文件下载的几种方法_delphi教程
  • 用delphi创建服务程序_delphi教程
  • 用delphi做一个有颜色属性的按钮_delphi教程
  • 用delphi开发dll来代替8581协议控制和采集华为psm—a10电源(二)_delphi教程
  • 用delphi编写系统进程监控程序_delphi教程
  • 西部数码虚拟主机

    友情链接
    CNNIC 西部数码
    万网 自助建站
    虚拟主机 asp空间
    域名注册 域名
    域名申请 主页空间
    论坛空间 网站空间
    国际域名 虚拟空间
    空间租用 DDOS防火墙
    成都主机托管 四川主机托管
    主机租用 服务器租用
    网站目录 自助建站
    虚拟主机 网址大全
    软件下载
    自助链接
    虚拟主机资讯 特价虚拟主机
    版权申明:本站文章均来自网络,如有侵权,请联系我们,我们收到后立即删除,谢谢!
    关于我们:站长天空:专业提供最新的站长资讯、在线教程、虚拟主机权威评测、虚拟主机性能对比、网站制作教程,开发教程,站长工具。包括网页制作教程、冲浪宝典、编程参考、操作系统、软件教学、行业动态等。
    特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有。
    发表评论 打印  刷新     关闭