有关TDataSet的研究
VCL 的数据库框架中有一个很重要的抽象类叫TDataSet ,它可以处理非BDE 的数据源。
鉴于Delphi的很多数据库感知控件都是以TDataSet作为接口和数据库连接的,为了重复使用Delphi现有的资源,已经使我们的小型数据库能更加健壮,更加可移植,已经提出公共接口,针对接口编程,因此决定将原来的GPF控件改写由TDataSet继承。
TDataSet本身自己已经封装了记录缓冲系统。Buffers就是记录它的记录缓冲区的。BufferCount记录可是用缓冲区的个数。但是Buffers的个数比BufferCount多一个。这多出来的一个是TDataSet用来作为临时缓冲区的,即TempBuffer。虽然TDataSet已经封装了,但是对缓冲区的所有实现还得在派生类里实现。但所有在外面可以实现的都是对单个记录缓冲区的。如:为记录缓冲区申请空间,释放空间,初始化缓冲区,以及一些和缓冲区有关的操作。这些在《Delphi 5 开发人员指南》中30章第四节“扩展TDataSet中已经有了很好的说明。
图1:记录缓冲列表结构
由于TDataSet是个抽象类,所以必须在派生类中实现所有的抽象方法。实现这些抽象方法也正是实现我们的GPF(现改名为GDB)。因为正是这些抽象方法实现了DataSet和数据库数据的连接。最基本的就是InternalOpen和InternalClose两个方法。另外要实现使用Field,就得实现GetFieldData,SetFieldData。这里有一点要说明的是在Fields中的Field的顺序(即用Fields[I]访问的顺序)和数据库中字段的循序是不一样的。我在这里就曾经犯过错误。我的解决方法是使用Field中的属性FieldNo,这个属性可以记录这个字段的物理位置。然后使用FieldByNumber来访问字段。(是不是有点麻烦?我也是没有办法的办法)。当然了,你也可以用一个列表按原始顺序记录Fields的。
TDataSet里的记录号即RecNo这个属性是用来表示当前可以访问的所以记录中按当前访问顺序的第RecNo个记录。感知控件的显示也依赖于这个属性。它不是用来表示记录的物理顺序的。因此,在实现的时候,内部管理记录时要另外使用一个成员变量来表示,相当于一个内部游标。
另外有几个在《开发指南》中没有提到的方法我想说明一下,这几个都与感知控件的显示有关,因此都在Public下的。
IsSquenced这个在TDataSet中Public下的函数,应该继承一下。该方法是用来表示记录的组织是否是连续紧凑的。这个特性在数据库感知控件中会使用到。比如DBGrid中的垂直滚动条的状态就是根据这个来决定的。如果该函数返回真,就是说记录是紧凑的,则滚动条会根据RecNo来决定具体位置。反之,如果是假,则滚动条只有三个状态,即最上、中间、最下。
CompareBookmark这个函数,使用来判断两个记录的大小的。同样,这个函数也与感知控件有关。在DBGrid选择了MultiSelect后,当选择了一条记录后,Grid会根据该函数的返回值来决定是否选中邻近的记录。其实一句话,就是说Grid在这种状态下,会选择所有的相同记录。
另外有一个函数,GetCanModify是实现特性CanModify的。覆盖一下可以用来控制DataSet的ReadOnly。
对在第三段我说的TempBuffer我想在补充几句。TempBuffer这个缓冲区具有很大的作用的,因为它拥有与其他缓冲区相同的特性与操作,而且它对你来说是“生存期自管理”的。这一点在你实现查询操作时会发现很有用。你应该已经有了比较两条记录的函数,但是在查询时,你只有一条记录以及查询条件可以使用。这是你可以将你的查询条件转换成记录,而这条记录就可以放到TempBuffer中。这样你的实现就会变得很统一,因为你做的只是两条记录的比较。
TDataSet另外封装了一点就是书签功能(Bookmark)。一般情况下,Bookmark只要有两个成员变量就行了。一个是Data(TBookmark类型),一个是Flag(TBookmarkFlag)。
Data使用来记录你可以借此而访问到该记录的值。这是个Pointer类型的变量。因此,你可以把它作为整数使用(如存放记录号,我就是这样用的),也可以指向一个记录的指针,甚至于一个记录。而Flag是用来记录当前标签的位置。在Delphi中TbookmarkFlag是这样定义的:TBookmarkFlag = (bfCurrent, bfBOF, bfEOF, bfInserted);其中bfCurrent是表示当前位置,即有效Data。BfBOF和bfEOF表示首尾位置。BfInserted表示刚刚插入还没有提交(Post)的记录。
理解了Bookmark所需要的成员变量以及其意义。我们看一下在TDataSet中Delphi是如何使用Bookmark的。为了便于说明,我先介绍一下我在实现GDB时使用的Bookmark。
下面是结构:
PGDBBookmarkInfo = ^TGDBBookmarkInfo;
TGDBBookmarkInfo = record
BookmarkData: Integer;
BookmarkFlag: TbookmarkFlag;
End;
首先要解决的是Bookmark放在什么地方。这其实对外也是看不见的。上面讲到记录缓冲区,其实Bookmark就是接在记录缓冲区后的。或者更简单的说,整个缓冲区是由记录和Bookmark(TGDBBookmarkInfo结构)组成的。
记录缓冲 |
书签(TGDBBookmarkInfo) |
图2: 记录缓冲区结构
对缓冲区的操作是在TDataSet的派生类里实现的。有几个抽象方法SetBookmarkFlag,SetBookmarkData,GetBookmarkData,GetBookmarkFlag。这几个方法是直接对缓冲区操作的。
TDataSet就是利用这些方法对Bookmark进行操作的。不过,一般设置Bookmark都是在继承类里实现的。实现的位置其实是可以想象的到的。由于Bookmark和记录缓冲区在一起,那么在对记录缓冲区操作的时候(读记录到缓冲区的时候),也就是GetRecord函数中,你可以将与该记录关联的Bookmark写入。正如上面讨论到的,你必须在BookmarkData中填入相应的值。如我这里,就可以填入记录号。以后可以用记录号来访问该记录。
InternalGotoBookmark就是利用BookmarkData来定位到记录的。InternalSetToRecord也是类似的操作。
另外一个由数据感知控件频繁使用的就是CompareBookmarks。它的两个参数就TBookmark类型的。你就是根据这个指针来访问你存放的数据。象我的实现就可以使用Integer(Bookmark1^)来使用。另外,按照我的实现,GetRecNo也可以通过对ActiveBuffer(记录当前活动的缓冲区)的访问来获得的。