用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实现数据集的简单对象化的更多相关文章
- 用DELPHI的RTTI实现对象的XML持久化
去年我花了很多时间尝试用DELPHI进行基于XML的WEB应用开发.起初的设想是很美好的,但结果做出来的东西很简陋.一部分原因就在于XML到Object之间的数据绑定实现太麻烦(另一部分是因为对XSL ...
- Delphi 的RTTI机制浅探3(超长,很不错)
转自:http://blog.sina.com.cn/s/blog_53d1e9210100uke4.html 目录========================================== ...
- DELPHI中完成端口(IOCP)的简单分析(4)
DELPHI中完成端口(IOCP)的简单分析(4) 在我以前写的文章中,一直说的是如何接收数据.但是对于如何发送数据却一点也没有提到.因为从代码量上来说接收的代码要比发送多很多.今天我就来写一下如 ...
- DELPHI中完成端口(IOCP)的简单分析(3)
DELPHI中完成端口(IOCP)的简单分析(3) fxh7622关注4人评论7366人阅读2007-01-17 11:18:24 最近太忙,所以没有机会来写IOCP的后续文章.今天好不容易有 ...
- DELPHI中完成端口(IOCP)的简单分析(2)
DELPHI中完成端口(IOCP)的简单分析(2) 今天我写一下关于DELPHI编写完成端口(IOCP)的工作者线程中的东西.希望各位能提出批评意见.上次我写了关于常见IOCP的代码,对于IOCP ...
- DELPHI中完成端口(IOCP)的简单分析(1)
DELPHI中完成端口(IOCP)的简单分析(1) 用DELPHI开发网络代码已经有一段时间了! 我发现在网上用VC来实现完成端口(IOCP)的代码很多,但是使用DELPHI来实现的就比较少了.对 ...
- TersorflowTutorial_MNIST数据集上简单CNN实现
MNIST数据集上简单CNN实现 觉得有用的话,欢迎一起讨论相互学习~Follow Me 参考文献 Tensorflow机器学习实战指南 源代码请点击下方链接欢迎加星 Tesorflow实现基于MNI ...
- 关于Delphi中多线程传递参数的简单问题
http://bbs.csdn.net/topics/390513469/ unit uThread; interface uses Classes; type Th = class(TThread) ...
- ArcGIS 网络分析[1.3] 在个人地理数据库中创建网络数据集/并简单试验最佳路径
上篇使用shp文件创建网络数据集,然而在ArcGIS 9中就支持地理数据库了,数据库的管理更为科学强大. 本篇就使用个人地理数据库进行建立网络数据集,线数据仍然可以是1.1中的线数据,但是我做了一些修 ...
随机推荐
- 【解决ViewPager在大屏上滑动不流畅】 设置ViewPager滑动翻页距离
在项目中做了一个ViewPager+Fragment滑动翻页的效果,在模拟器和小米手机上测试也比较正常.但是换到4.7以上屏幕测试的时候发现老是滑动失效. 因为系统默认的滑动策略是当用户滑动超过半屏之 ...
- TCP/IP之DNS域名解析系统
DNS系统是一个分布式的数据库,当一个数据库发现自己并没有某查询所需要的数据的时候,它将把查询转发出去,而转发的目的地通常是根服务器,根服 务器从上至下层层转发查询,直到找到目标为止.DNS还有一个特 ...
- 【DWR】Annotation入门
DWR简介:http://baike.baidu.com/view/73492.htm?fr=aladdin DWR2.0以后新增加了JDK5的注解(Annotation)功能,使用注解功能之后可以从 ...
- iOS 判断有无网络连接
众所周知,我们在开发APP时,涉及网络连接的时候,都会想着提前判断一下当前的网络连接状态,如果没有网络,就不再请求url,省去不必要的步骤,所以,这个如何判断?其实很简单. 前提:工程添加:Syste ...
- c++, 派生类的构造函数和析构函数 , [ 以及operator=不能被继承 or Not的探讨]
说明:文章中关于operator=实现的示例,从语法上是对的,但逻辑和习惯上都是错误的. 参见另一篇专门探究operator=的文章:<c++,operator=>http://www.c ...
- Visual Studio 2012中编写C程序
换了win7系统后,突然发现VC++6.0不兼容了,我听说有的同学的行,反正我是不行. 那就用VS2012呗.... 我们来看看怎么用: 打开文件->新建->项目,新建一个项目 选择win ...
- SQL Server 基础 05 多链表查询和子查询
连接查询 值得注意的是:字段前必须加表名,以便混淆 -- 多表连接查询和子查询 select * from dbo.stu_info ,dbo.sname2 -- 加连接规则的查询 where se ...
- 几个BCB例子
http://blog.163.com/tab_98/blog/static/11924097201511274543737/
- 深入JDK源码之Arrays类中的排序查找算法(转)
原文出处: 陶邦仁 binarySearch()方法 二分法查找算法,算法思想:当数据量很大适宜采用该方法.采用二分法查找时,数据需是排好序的. 基本思想:假设数据是按升序排序的,对于给定值x,从序列 ...
- PHP urlencode()和rawurlencode()使用和区别
string urlencode ( string $str ) 此函数便于将字符串编码并将其用于 URL 的请求部分,同时它还便于将变量传递给下一页. 返回值 返回字符串,此字符串中除了 -_. 之 ...