【问题标题】:How to call a function with void return using Firedac FDConnection Component in Delphi XE5?如何在 Delphi XE5 中使用 Firedac FDConnection 组件调用具有 void 返回的函数?
【发布时间】:2018-07-14 13:30:18
【问题描述】:

我最近开始在 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 引发异常类 带有消息“[FireDAC][Phys][PG][libpq] 错误的 EPgNativeException: 失败”。

在第二次运行时,我收到了违规访问错误:

项目 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);

...

忽略之前的错误并重试,显示另一个错误...

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

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

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

【问题讨论】:

  • 我无法在东京使用 PostgreSQL 10.1 重现这一点。但我想我知道为什么。在FDFree(FExecSQLTab) 之后似乎还有一个悬空指针。在东京有FDFreeAndNil(FExecSQLTab)
  • 太棒了!我制作了该文件的副本并按照您所说的进行了更改......它按预期工作。非常感谢你。显然,在以前版本的 IDE 中制作的应用程序的稳定性并不让 embarcadero 感兴趣
  • 不客气! EBMT 的典型做法是不关心修复传播到旧版本。
  • @Victoria:不错的收获!为什么不将其发布为答案?
  • @Victoria:很高兴你有时间去做。 +1

标签: database postgresql delphi firedac


【解决方案1】:

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

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

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 版本中完成的。

【讨论】:

  • 旁注;这并不像人们想象的那样明显,但是 PostgreSQL 的函数返回 void 只返回一个字段的元组,其值为 void(类似于 NULL),因此我说 ExecSQLScalar 很好。
猜你喜欢
  • 2020-01-18
  • 1970-01-01
  • 1970-01-01
  • 2017-02-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-13
相关资源
最近更新 更多