再谈 Firebird / Interbase 自增字段和 FireDAC 以及 ClientDataSet

本文详细探讨了如何在Firebird数据库中利用FireDAC的特性,尤其是FdQuery组件,配合生成器实现自增字段的自动增量,并介绍了四种有效的方法,包括触发器、存储过程、FdQuery配置和ClientDataSet的刷新策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

之前我有篇文章谈到这个:

InterBase、FireBird 的自增字段和 FireDAC 数据库控件_pcplayer的博客-优快云博客

最近做了一些进一步的研究,以下是研究结果。

FireDAC 的要点:

1. FdQuery 采用 select 语句打开表后,默认,是可以编辑的,然后它的 Post 会自动将编辑的数据保存回到数据库;

2. FdQuery 的属性 CacheUpdates 默认是 False 的,这时候 Post 是可以直接保存到数据库的。如果是 True 则需要执行 FdQuery1.ApplyUpdates 才能保存到数据库;

3. FdQuery 的属性:UpdateOptions / FetchGeneratorsPoint 默认是 gpDeferred; 如果直接在 FdQuery 里面插入数据(比如绑定一个 DBGrid),将这个属性设置为 gpImmediate ,同时设置 UpdateOptions / AutoIncFields 属性为指定的字段,再同时设置 UpdateOptions / GeneratorName 为指定的 FireBird 的生成器,则运行期插入数据后,该字段自动获得生成器创建的数字;

4. FdQuery 的属性:UpdateOptions / FetchGeneratorsPoint 默认是 gpDeferred; 如果直接在 FdQuery 里面插入数据(比如绑定一个 DBGrid),将这个属性设置为 gpDeferred(采用默认值) ,同时设置 UpdateOptions / AutoIncFields 属性为指定的字段,再同时设置 UpdateOptions / GeneratorName 为指定的 FireBird 的生成器,则运行期插入数据后,该字段自动填入 -1, -2 等值;当它是 CachedUpdates 为 True 时,它一直填入负数,直到真正提交;如果为 False,则因为它 POST 就写入数据库,因此只有 -1,插入新记录导致 -1 的记录 Post 使得 -1 马上变成生成器创建的新编号;

5. 在 4 的基础上,如果 ClientDataSet 绑定这个 FdQuery,并且,在对应的 DataSetProvider 里面,设置属性 ResolveToDataSet 为True也就是提交给 FdQuery 去保存,在 ClientDataSet 中,可以插入多条记录,采用 -1,-2,-3 的方式,然后提交后,因为 DataSetProvider 不是自己直接提交给数据库,而是提交给了 FdQuery,使得 FdQuery 自己去提交到数据库,然后获得了新的编号;但是,单纯的 ClientDataSet 的提交,虽然 FdQuery 写入数据库并从生成器获得了新编号,但并不会返回给 ClientDataSet,这里即便是给 DataSetProvider.Options.poPropogateChanges 设置为 True 也没用。因此, ClientDataSet 提交后要刷新,必须执行一次 ClientDataSet1.Refresh;

5.1. 这里如果把 FdQuery 的属性 UpdateOptions / FetchGeneratorsPoint 设置为 gpImmediate ,那么,上述 ClientDataSet 插入的 -1, -2 等记录提交时,FdQuery 并不会去从生成器取数据,而是直接保存入数据库,结果就是数据库里面存入了 -1, -2 这样的记录。

5.2. 总结:如果直接编辑 FdQuery 并且要在插入数据时马上看到编号,设置 UpdateOptions / FetchGeneratorsPoint 为 gpImmediate;但如果要让 ClientDataSet 来提交,则需要设置为 gpDeferred 这个默认值。并且在 ClientDataSet 提交后需要 Refresh 一下,客户端才能看到真正的自增字段的值。

Firebird 数据库自增字段:

1. Firebird 数据库的自增字段,由两个东西合成:A. 一个生成器产生唯一序号数字;B. 一个 Trigger 触发器,设计为该表的 Type 为 Beofre,Event 为 Insert ,里面的代码则是为该字段从生成器取一个数字。这个触发器的创建代码如下:

CREATE TRIGGER BI_A3_ID FOR A3 ACTIVE BEFORE INSERT POSITION 0 AS 
BEGIN
  IF (NEW.ID = null) THEN NEW.ID = GEN_ID(A3_ID_GEN, 1); 
END

2. 触发器可以设置为 Active 或者非 Active;

3. 有了这个触发器,则新插入的记录,会自动为自增字段增加一个数字。

4. 但是,使用触发器,DataSetProvider 提交数据时,没办法从触发器活动新创建的数字,导致无法将数字返回客户端。当然,如果客户端直接整个表 Refresh 也没问题(ClientDataSet.Refresh)。但客户端直接整个表 Refresh 可能会导致网络传输数据量巨大,应该尽量避免。

---------------

结论:想偷懒是不行的。

在 FireBird 里面使用生成器创建序数,在 ClientDataSet 一端使用,有4个办法:

1. 网络上其他人的文章里面提到的,在 DataSetProvider1.AfterUpdateRecord 里面,通过数据库的特定方法获取表的最新记录的自增字段的值。这个办法,我担心并发插入时,获取到的不是自己当前插入的这条记录的值;暂时不考虑使用;

2. 什么都不做,数据库里面增加一个触发器,帮忙产生新序数直接插入到表里面,在 ClientDataSet 提交成功后,调用 Refresh 来获得新的数据。问题是,这样是刷新整个表,看起来和 Close / Open 差不多。可能网络传输数据比较多。当然如果表不大数据不多的时候,用这个办法少写代码,省事

3. 数据库里面没有触发器,而是在服务器端 DataSetProvider.BeforeUpdateRecord 的地方,自己用存储过程从数据库获得这个序数,写入 Delta 的 NewValue 里面。这样做,提交给数据库的数据是生成器创建的数据而不是客户端的 -1,-2,然后这个数据可以自动回到客户端(前提是设置 DataSetProvider.Options.poPropogateChanges 为 True);

4. 使用 FireDAC 的 FdQuery 对生成器的支持,不需要 Firebird 数据库有触发器:

4.1. FdQuery 按照前述方式,设置其 UpdateOptions 的属性:A. AutoIncFields 属性为自增字段的名字;B. FetchGeneratorsPoint 为 gpDeferred;C. GeneratorName 下拉选择对应的 Firebird 数据库的生成器的名字;

4.2. FdQuery 的 CachedUpdates 使用默认的 False,此时它自己 POST 就会将改动的记录写回服务器;

4.3. DataSetProvider.BeforeApplyUpdates 事件里面先把 FdQuery.Open 打开;

4.4. DataSetProvider 的属性:A. ResolveToDataSet 设置为 True 使得它将改动提交给 FdQuery1 而不是自己去执行写服务器的 SQL 语句;B. 属性 Options.poPropogateChanges 设置为 True 用来传递对 Delta 的修改;

4.5. DataProvider 的 AfterUpdateRecord 里面,写以下代码:

DeltaDS.FieldByName('ID').ReadOnly := False; 
DeltaDS.FieldByName('ID').NewValue := FdQuery1.FieldByName('ID').AsInteger;

4.6. 上述做法,最终就是:DataSetProvider 将客户端新插入的数据丢给了 FdQuery1,然后 FdQuery1 自动 Post 了,写入数据库;FdQuery1 在 Post 的时候,自动通过设置给它的生成器获得了新的序数,写入数据库的记录是正确的序数;然后 FdQuery1 里面的新的序数,通过 DataSetProvider 又返回给了 ClientDataSet;

又及:

经过测试,想在服务器端将新的编号传递给客户端,修改 Delta.FieldByName('ID').Value 没用。必须是修改 NewValue;

如果提交成功后来修改,也是可以的。问题是,如果采用触发器,则提交成功后来修改,也无法获得这条记录在数据库中的值。除非,多写几行代码,通过其它字段的值来在数据库中定位到这个字段。这样就要保证其它字段在数据库中也必须是唯一的。不过,通常的表,也应该唯一,否则两个不同序号的记录,其它字段的值完全相同,没有存在的意义。

结论:

如果采用上述第四的方式,则服务器端减少使用了存储过程获得生成器最新序数的代码。但因为有一堆的设置,并且依赖 FdQuery 的属性,使得单纯读代码,就无法知道这个数字如何获得的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值