我最近开始在Delphi XE5中使用FDConnection组件的[ExecSQLScalar]
1和[ExecSQL]
2方法。无需仅为简单查询或执行而构建FDQuery之类的Dataset对象,就非常方便。
但是,在执行带有void返回的函数时,我有一个奇怪的问题,该函数具有内部验证,可以在其中生成异常。我正在使用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);
...
忽略上一个错误,然后重试,则显示另一个错误...
项目TZConnectionDEMO.exe引发了带有以下内容的异常类EFDException 消息'[FireDAC] [DatS] -24。行未嵌套”。
在搜索中,我没有找到对此错误的回应。我发现我的错误是使用FDConnection组件的ExecSQLScalar函数调用bank raise_exception函数。因此,我尝试使用FDConnection.ExecSQL
,但正如我想象的那样,如果参数中有SELECT
子句,则无法使用它。
是否有更好的方法使用FDConnection.ExecSQL调用带有空返回的函数? BUG会出现在组件中吗?还是打这样的电话不正确?
答案 0 :(得分:4)
在这种情况下,使用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版本中完成的。