前言

基于 MIDAS 的多层架构的数据库程序,客户端是 ClientDataSet,数据库服务器是 FireBird,服务器端采用 FireDAC 的数据库控件,如何处理数据库端的自增字段。

之前我有博客文章介绍具体做法。这里更新一下另外一种做法,需要写的代码最少

设计期设置:

FireBird 数据库设置

数据库的自增字段,在设计期,使用工具,直接创建自增字段。其实这里 FireBird 创建了 3 个东西:

  1. 一个整数字段;
  2. 一个生成器;
  3. 一个触发器;

FireBird 本身没有自增字段,使用工具创建自增字段,工具为这个字段创建了一个触发器,当插入新纪录时,由触发器去调用生成器(这个是 FireBird 和 InterBase 独有的东西)去创建最新的自增数字并填入新插入的记录。触发器代码如下:

CREATE TRIGGER BI_TABLE4_ID FOR TABLE4
ACTIVE BEFORE INSERT
POSITION 0
AS
BEGINIF (NEW.ID IS NULL) THENNEW.ID = GEN_ID(TABLE4_ID_GEN, 1);
END;

服务器端 FireDAC 的设置

为 FdQuery1 增加固定字段;假设这个自增字段名字是:ID (我做测试的数据库,对应的表的自增字段,命名为 ID)

在 FdQuery1 的字段编辑器窗口里面,选择 ID 字段,在属性窗口里面找到 ID 字段的属性:AutoGenerateValue

这个属性有3个下拉选项:arNone, arDefault, arAutoInc;

默认值是 arNone.

arNone: 什么都不做。一般的字段用这个。

arDefault:假设数据库的 ID 字段有默认值,对于 FdQuery1 来说,此属性选择这个值,意思是 FdQuery1 会采用数据库默认值;实际测试,发现不管填什么值,提交后会获得这个默认值。

arAutoInc:数据库如果是自增字段,则这里对于 FdQuery1 的对应字段,选择这个值;

arAutoInc 进阶解释:

  1. 新增加记录时,FdQuery1 的对应字段会自动出现 -1, -2 等值,无需我们填写;
  2. 对于 FdQuery1,其 ID 字段是自增字段;对于 FdQuery1 来说,如果属性 CachedUpdates 设置为 True,新增的记录保存后,ApplyUpdates 提交可以成功,并且新增记录的对应字段的 -1, -2 会自动变成数据库提交后的值,但是,FdQuery1 的 ChangeCount 依然大于 0,(可能是个 BUG),而且它没有 MergeChangeLog 方法导致无法清除已经提交的记录,因此,这种模式,无法使用;
  3. FdQuery1 的 CachedUpdates 属性的默认值是 False,在此情况下,FdQuery1 打开后,插入新的记录(因为 ID 的 AutoGenerateValue 属性设置为 arAutoInc 所以插入新记录,这个字段会自动填入 -1,-2),在执行 Post 后,会写入数据库,并且此记录的 ID 字段自动变成数据库自增的值;
  4. 综上,这里仅仅只需要设置 ID 字段的 AutoGenerateValue 属性为 arAutoInc 就可以了!
  5. 在以上设置的情况下,虽然新增记录,ID 字段自动填入了 -1, -2,但是,Post 的时候,FireDAC 并没有向数据库的对应字段插入值。这时候,触发器的代码里面,之前我修改为 IF NEW.ID < 0 THEN 就会导致 FireBird 数据库出现提交异常,说 ID 字段不能为 NULL,这也说明了 FireDAC 提交过去的数据,ID 字段没有值;但如果触发器的代码是以下代码,则此问题消失:
BEGINIF ((NEW.ID < 0) or (NEW.ID IS NULL)) THENNEW.ID = GEN_ID(TABLE3_ID_GEN, 1);
END

服务器端的 DataSetProvider 的设置:

属性 ResolveToDataSet 默认是 False,必须改为 True,让 DataSetProvider 把数据交给 FdQuery1 去提交,而不是由 DataSetProvider 自己去提交。

属性 Options 下拉展开,选择 poPropogateChanges (传播变化) -- 这个值默认没有勾选;这个属性勾选后,DataSetProvider 的数据被修改后,会在提交后,传递回客户端;

服务器端的代码

procedure TtestMasterDetail.DataSetProvider3AfterUpdateRecord(Sender: TObject;SourceDS: TDataSet; DeltaDS: TCustomClientDataSet; UpdateKind: TUpdateKind);
begincase UpdateKind ofTUpdateKind.ukInsert: DeltaDS.FieldByName('ID').NewValue := FdQuery3.FieldByName('ID').Value;TUpdateKind.ukModify:;TUpdateKind.ukDelete:;end;
end;

上述代码,是把 FdQuery3 在 Post 新记录到数据库成功以后自动获得的自增字段 ID 的值,返回给客户端。

客户端要做的事情:

客户端不需要做任何设置。仅仅需要为了操作者方便,自动为新记录加上负数的 ID 值。不写代码,让操作者手动填入负数值也行。自动填写的代码如下:

procedure TForm4.ClientDataSet1AfterInsert(DataSet: TDataSet);
beginwith ClientDataSet1 dobeginTag := Tag -1;FieldByName('ID').Value := Tag;end;
end;

到此,成功搞定。这个是目前最少写代码的办法。

测试结果:

客户端的 DBGrid1 里面,插入3条记录,因为上述代码,ID 字段的值,自动变成 -1,-2,-3;

执行 ClientDataset1.ApplyUpdates(0) 后,提交成功,马上看见 -1, -2, -3 的 ID 值自动变成由 FireBird 数据库自动为这个字段创建的自增的数字。结果很完美。

经过再三测试,另外一种更靠谱的做法如下

上述方法测试通过后,对于这个问题,我再次查阅了之前写的博客,发现 FdQuery 还有另外一种玩法,看起来更靠谱。

首先,上述设置 ID 字段的 AutoGenerateValue 的属性为 arAutoInc 的作法,虽然提交后能获取到数据库真正的自增的字段编号,但它是来自提交后重新从数据库 select 回来最新数据而获得。如果有多个客户端并发提交,可能会出现问题。

更好的办法

另外一种办法是让 FdQuery 直接去向数据库的生成器获得最新编号写入 FdQuery 的记录里面,然后再提交到数据库。这样做在有很多客户端并发提交时,更为靠谱。

类似做法是之前本人博客里面有提到的,使用一个存储过程来从生成器获取最新编号给 FdQuery 或者给客户端提交的 DataSetProvider.OnBeforeUpdateRecord 事件用于在数据真正提交给数据库之前修改字段值。但这个做法,需要:

1. 给数据库做一个存储过程;此存储过程去从生成器获取最新编号;

2. 程序里面需要有一个用于执行该存储过程的控件;

3. 程序里需要有一段代码,用于执行存储过程,并将获得的新编号赋予要提交的字段;

现在仅仅是对 FdQuery 的几个属性设置一下,不需要做存储过程,FdQuery 自己自动向数据库的生成器获取新编号,因此可以写更少的代码

更好的办法的具体做法

更好的办法是仅仅设置几个属性就可以搞定,更少写代码。这是因为 FdQuery 的功能带来的。

  1. 数据库的字段,为它创建一个生成器用于产生编号;但不需要触发器,也就是不需要做成自增字段;
  2. DataSetProvider3BeforeUpdateRecord 事件里面,将来自客户端提交的数据的 ID 字段清空;这里必须是空值,FdQuery 才会去从生成器获取新编号;而在客户端,新记录必须要有编号,才能多条新记录共存(因为编号字段肯定是一个不许重复的字段)。
  3. 设置 FdQuery1.UpdateOptions.FetchGeneratorsPoint 的属性为 gpDeferred
  4. DataSetProvider3AfterUpdateRecord 里面将 FdQuery3 的对应字段(ID 字段)值,赋予 DeltaDS.FieldByName('ID').NewValue 则将新编号返回客户端;
  5. DataSetProvier.Options.poPropogateChanges 设置为 True (设计期在属性列表里面勾选);
  6. DataSetProvider.ResolveToDataSet 属性设置为 True; (设计期在属性列表里面勾选);
  7. FdQuery 其它属性都可以用默认值。不要设置 CachedUpdates 为 True;
  8. FdQuery 的字段 ID 也不需要设置其 AutoGenerateValue 为 arAutoInc,保持默认的 arNone 就可以了。

上述 2 的代码:

procedure TtestMasterDetail.DataSetProvider3BeforeUpdateRecord(Sender: TObject;SourceDS: TDataSet; DeltaDS: TCustomClientDataSet; UpdateKind: TUpdateKind;var Applied: Boolean);
begincase UpdateKind ofTUpdateKind.ukInsert:beginDeltaDS.Edit;DeltaDS.FieldByName('ID').Clear;end;end;
end;

上述 3 的代码:

procedure TtestMasterDetail.DataSetProvider3AfterUpdateRecord(Sender: TObject;SourceDS: TDataSet; DeltaDS: TCustomClientDataSet; UpdateKind: TUpdateKind);
begincase UpdateKind ofTUpdateKind.ukInsert:beginDeltaDS.FieldByName('ID').NewValue := FdQuery3.FieldByName('ID').Value;end;TUpdateKind.ukModify:;TUpdateKind.ukDelete:;end;
end;

花了几个小时反复测试,终于搞清楚 FireBird 结合 FireDAC 该如何处理自增字段的问题了。

更新:

上述方法在 D10.3. 测试通过;在 D10.4.2 下面,上述方法会出问题,暂时还没找到问题的解决办法。

在 D10.4.2.下面,如果执行了上述 2 的代码,则当执行上述 3 的代码时,Delphi 会弹出异常提示。如果不执行上述 2 的代码,执行上述 3 的代码,则不会有异常提示。

但是,如果不执行上述 2 的代码,也就是客户端提交后,写入 FdQuery 的记录的 ID 字段依然是客户端的负数编号,而不是空值,带来的错误结果有点不符合逻辑。错误现象是:

1. 客户端第一次提交,服务器端的 FdQuery 依然可以向生成器获取新编号值;因此第一次提交会成功;

2. 客户端如果提交后,再次插入新记录,再次提交,则服务器端不会向生成器获取新编号,而是直接将来自客户端的负数编号的记录写入数据库。

3. 奇怪的地方:向数据库的生成器获取新编号是服务器端的代码,但这种情况下,不用重启服务器端,重启客户端,又可以获得第一次的成功提交,第二次,第三次则又是失败的。

因此,上述方法在 D10.4.2 可能不能使用。

再谈 FireBird 的自增字段用 FireDAC 来处理相关推荐

  1. 再谈HTTP2性能提升之背后原理—HTTP2历史解剖

    即使千辛万苦,还是把网站升级到http2了,遇坑如<phpcms v9站http升级到https加http2遇到到坑>. 因为理论相比于 HTTP 1.x ,在同时兼容 HTTP/1.1 ...

  2. 再谈符号间干扰(一)

    在对话通信原理系列相关博文中,有这么一篇博文:通信系统之信道,这篇博文里面已经讲过符号间干扰(ISI),发生符号间干扰的原因在于信号带宽大于相干带宽,同一个意思的表达为:发送符号的周期小于最大时延扩展 ...

  3. db2主键自增和oracle,oracle_浅析常用数据库的自增字段创建方法汇总,DB2复制代码 代码如下:CREATEnbsp - phpStudy...

    浅析常用数据库的自增字段创建方法汇总 DB2 CREATE   TABLE  T1 ( id  INTEGER   NOT   NULL  GENERATED ALWAYS  AS   IDENTIT ...

  4. 劫起|再谈Linux epoll惊群问题的原因和解决方案

    原作者:dog250,授权发布 重新整理: 极客重生 文章有点长,可以三连收藏慢慢看 缘起 近期排查了一个问题,epoll惊群的问题,起初我并不认为这是惊群导致,因为从现象上看,只是体现了CPU不均衡 ...

  5. mysql 主键 下一个值_INNODB自增主键的一些问题 vs mysql获得自增字段下一个值

    root@localhost : test 04:23:28>show variables like 'innodb_autoinc_lock_mode'; +----------------- ...

  6. mysql 修改自增字段起始值不生效_Mysql数据库基本介绍

    1.mysql不分大小写, 他是存放数据的数据库管理系统字符集使用utf-8,python分大小写,r也分大小写,常用的数据库是关系型数据库,workbench编辑工具,Ctrl+回车执行: 2.一个 ...

  7. INNODB自增主键的一些问题 vs mysql获得自增字段下一个值

    今天发现 批量插入下,自增主键不连续了....... InnoDB AUTO_INCREMENT Lock Modes This section describes the behavior of A ...

  8. php mysql 字段自增_MySQL自增字段取值的详细介绍(附代码)

    本篇文章给大家带来的内容是关于MySQL自增字段取值的详细介绍(附代码),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. 1 前言 本文来自回答思否网友的一个问题,这个网友新建了一张表 ...

  9. Unity教程之再谈Unity中的优化技术

    这是从 Unity教程之再谈Unity中的优化技术 这篇文章里提取出来的一部分,这篇文章让我学到了挺多可能我应该知道却还没知道的知识,写的挺好的 优化几何体 这一步主要是为了针对性能瓶颈中的" ...

最新文章

  1. 乔氏西去,敬告各位!
  2. 快速搭建Java 17环境并玩转Record特性
  3. 802.11ac和SD-WAN有什么联系?
  4. webpack使用加载器来加载CSS样式
  5. python的两种循环结构_python分支和循环结构
  6. 分享百度文库提交成功的八大因素
  7. 固有属性与自定义属性
  8. MySQL数据库(六) 一一 基本操作之事物和索引
  9. RocketMQ高性能通信实现机制源码精读
  10. Linux上通过SUU更新Dell服务器固件
  11. lstm原文_对时间序列分类的LSTM全卷积网络的见解
  12. 互联网大数据中标签的类型
  13. Android 如何通过拨号盘暗码启动你的应用
  14. SEM竞价推广创意快速撰写的方法,智能创意制作
  15. Teams Meeting 实时事件通知
  16. swagger转换成word文档
  17. 如何用计算机打出下划线,电脑下划线怎么打?下划线怎么输入出来
  18. 如何去除 aspose.cells 水印
  19. 宝塔一键安装wordpress
  20. 边缘设备、系统及计算杂谈(3)—edgex-go,了解一下

热门文章

  1. 把极坐标化为直角坐标c语言,极坐标方程化为直角坐标方程
  2. 男女生在宿舍熄灯后,聊天内容的差距也太大了……
  3. 双麦阵列回音消除及降噪模块 A-47设计用和强噪音下实测效果视频
  4. 如何使用Python将语音转换为文本,你知道吗?
  5. 【毕业设计】树莓派智能捡垃圾机器人 - 机器视觉 单片机 物联网
  6. Linux系列---【验证端口网络策略是否通的几种方式】
  7. 免费不限时长的语音转文字软件——Word365
  8. 中国核心生态区类型及土地利用数据有哪些,如何进行获取
  9. cocos2d动作家族族谱
  10. VS2012 番茄助手 Visual Assist X下载及安装