用DELPHI的RTTI实现数据集的简单对象化

CND8学院 Delphi教程 发布日期:2008年07月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;
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参数是在编译时就确定地传给函数了,并没有一个动态的表来记录,所以只能采用现在这样的方法来将就。

用DELPHI的RTTI实现数据集的简单对象化相关推荐

  1. 用DELPHI的RTTI实现对象的XML持久化 【转】

    去年我花了很多时间尝试用DELPHI进行基于XML的WEB应用开发.起初的设想是很美好的,但结果做出来的东西很简陋.一部分原因就在于XML到Object之间的数据绑定实现太麻烦(另一部分是因为对XSL ...

  2. R语言e1071包中的支持向量机:仿真数据(螺旋线性不可分数据集)、简单线性核的支持向量机SVM(模型在测试集上的表现、可视化模型预测的结果、添加超平面区域与原始数据标签进行对比分析)、如何改进核函数

    R语言e1071包中的支持向量机:仿真数据(螺旋线性不可分数据集).简单线性核的支持向量机SVM(模型在测试集上的表现.可视化模型预测的结果.添加超平面区域与原始数据标签进行对比分析).如何改进核函数 ...

  3. Dolphin social network——海豚社会网络数据集的简单研究

    1.海豚社会网络数据摘要: Lusseau等在新西兰对62只宽吻海豚的生活习性进行了长时间的观察,他们研究发现这些海豚的交往呈现出特定的模式,并构造了包含有62个结点的社会网络.如果某两只海豚经常一起 ...

  4. Tensorflow— MNIST数据集分类简单版本

    代码: import tensorflow as tf from tensorflow.examples.tutorials.mnist import input_data#载入数据集 #当前路径 m ...

  5. ciaodvd数据集的简单介绍_基于注意力机制的规范化矩阵分解推荐算法

    随着互联网技术的发展以及智能手机的普及, 信息超载问题也亟待解决.推荐系统[作为解决信息超载问题的有效工具, 已被成功应用于各个领域, 包括电子商务.电影.音乐和基于位置的服务等[.推荐系统通过分析用 ...

  6. 【opencv有趣应用】opencv + YOLOV3 + COCO数据集的简单应用

    谈起深度学习进行目标检测,我们能想到的一个分支就是端到端的YOLO系列. 我们之前接触过YOLO,也学习过YOLO, 文章如下: https://blog.csdn.net/qq_29367075/a ...

  7. 数据挖掘_基于balance-scale数据集的简单分类任务验证性实验

    目录 一,题目描述 二,实验准备 1,数据集下载 1.1 常用数据集链接 1.2 UCI数据集 1.3 Kaggle 2,所需基础知识 2.1 分类的方法 2.2 去重 2.3 扩增数据集 2.4 特 ...

  8. 使用DL4J对CWRU数据集进行简单分类

    目录 0. 前言 1. 关于CWRU数据集 2. 数据读取 3.数据预处理 4. 训练 0. 前言 最近开始搞智能故障诊断方面的工作,一上来面对的就是要各种炼丹.虽然众所周知在炼丹方面是python比 ...

  9. Karate空手道俱乐部数据集的简单处理

    1.Karate数据集简介 Karate空手道俱乐部复杂网络是常用于复杂网络社区发现研究的网络,该网络共有 34个节点和78条边,其中34个节点表示某空手道俱乐部的 34 名成员,节点之间的边表示两个 ...

最新文章

  1. python实践3:cursor() — 数据库连接操作
  2. hdu5387(模拟)
  3. java开发属于itsm吗_【行业】IT服务管理(ITSM):IT行业变革的思考(1)
  4. 牛客网编程题05--进制转换
  5. Apache OpenOffice 漏洞使数千万用户易受代码执行攻击
  6. 解决计算机系统问题开发的软件是,为解决计算机系统问题而开发的软件是。
  7. java反射机制原理详解
  8. mtk无线网卡 linux,在树莓派上使用基于MT7601的无线网卡(如360/百度/腾讯Wifi)
  9. Euraka学习笔记
  10. 【Dos默认路径设置】
  11. 李密(582~619)
  12. 深入理解 OC/C++ 闭包
  13. 从网线到网络设备是如何工作的
  14. 微信小程序:工具配置 project.config.json
  15. 2023年英语二大作文押题猜想(达立易考)
  16. python获取2020年国家统计局省市县三级数据
  17. fpu测试_浮点运算单元FPU能给电机控制带来什么?
  18. foter 图像处理APP_黑科技!图像识别app开发的用处原来这么多?
  19. 2022年中国医疗设备电源市场现状研究分析与发展前景预测报告
  20. 一些能够提高程序员办公效率的办公桌

热门文章

  1. 通用类别目录Global Catalog[为企业维护windows server 2008系列五]
  2. hdu-Frosh Week(树状数组)
  3. redis主从架构宕机问题手动解决
  4. 18_使用react脚手架构建应用
  5. Jconsole配置与连接
  6. BZOJ1720: [Usaco2006 Jan]Corral the Cows 奶牛围栏
  7. ArrayList、LinkedList、Vector的区别。
  8. 腾讯云 wdcp ip:8080打不开,wdcp phpmyadmin打不开
  9. 七月算法--12月机器学习在线班-第七次课笔记—最大熵
  10. Asp.Net Mvc之模型注解