首页 > 解决方案 > 如何在 Delphi XE5 中使用 Firedac FDConnection 组件调用具有 void 返回的函数?

问题描述

最近开始在Delphi XE5中使用FDConnection组件的[ExecSQLScalar]1[ExecSQL]2方法。不需要构建一个 Dataset 对象非常方便,比如 FDQuery 只用于简单的查询或执行。但是,在执行一个带有 void return 的函数时,我遇到了一个奇怪的问题,该函数具有可以生成异常的内部验证。我正在使用 Postgres 数据库。

CREATE FUNCTION can_be_exception()
  RETURNS void AS
$$
BEGIN
    RAISE EXCEPTION E'fail';
END;
$$
  LANGUAGE plpgsql STABLE;

在delphi中,我调用ExecSQLScalar函数...

FDConnection1.ExecSQLScalar('select 1');
FDConnection1.ExecSQLScalar('select can_be_exception()');

第一次运行时,我收到以下错误:

项目 TFDConnectionDEMO.exe 引发异常类 EPgNativeException,消息为“[FireDAC][Phys][PG][libpq] 错误:失败”。

在第二次运行时,我收到一个 Violation Access 错误:

项目 TFDConnectionDEMO.exe 引发异常类 $C0000005,并带有消息“0x00000000 处的访问冲突:读取地址 0x00000000”。

显然错误发生在单元下面的行中FireDAC.Comp.Client

function TFDCustomConnection.ExecSQLScalar(const ASQL: String;
  const AParams: array of Variant; const ATypes: array of TFieldType): Variant;
var
  oCmd: IFDPhysCommand;
begin
  oCmd := BaseCreateSQL;
  try
    if BasePrepareSQL(oCmd, ASQL, AParams, ATypes) or (FExecSQLTab = nil) then begin
      FDFree(FExecSQLTab);

...

忽略上一个错误并重试,显示另一个错误...

项目 TZConnectionDEMO.exe 引发异常类 EFDException,消息为“[FireDAC][DatS]-24。行没有嵌套'。

搜索,我没有发现这个错误的响应。我认为我的错误是使用 FDConnection 组件的 ExecSQLScalar 函数调用银行 raise_exception 函数。所以我尝试使用并且正如我想象的那样,如果参数中有子句,FDConnection.ExecSQL则不能使用它。SELECT

有没有更好的方法来使用 FDConnection.ExecSQL 调用具有 void 返回的函数?BUG 会在组件中吗?还是打这种电话不正确?

标签: databasepostgresqldelphifiredac

解决方案


在这种情况下使用ExecSQLScalar很好。这当然是一个错误(至少在 Delphi 10.2.3 中已经修复)。正如您正确指出的那样,问题在于使用FDFree过程释放FExecSQLTab字段持有的表存储对象实例。

我没有 Delphi XE5 源代码,但也许你可以在里面看到类似的东西(关于发生的事情的评论由我添加):

if BasePrepareSQL(oCmd, ASQL, AParams, ATypes) or (FExecSQLTab = nil) then
begin
  FDFree(FExecSQLTab); { ← directly calls destructor if object is not nil }
  FExecSQLTab := oCmd.Define; { ← no assignment if command execution raises exception }
end;

问题是,当在存储表定义阶段( oCmd.Define )执行 SQL 命令引发异常时,对先前销毁的存储表对象实例(由FDFree)的引用仍存储在FExecSQLTab字段中(作为悬空指针)。

然后,当以这种方式执行不同的命令时,仅针对该悬空指针调用FDFree过程。因此访问冲突。

纠正此问题的方法是替换行,例如:

FDFree(FExecSQLTab);

经过:

FDFreeAndNil(FExecSQLTab);

这是在后来的一些 Delphi 版本中完成的。


推荐阅读