FireDAC映射规则不适用于参数?

时间:2017-09-26 13:13:19

标签: delphi parameters firedac delphi-10.2-tokyo

我有一个TFDConnection到FireBird数据库,为了与以前的数据访问技术(SQLDirect)向后兼容,我应用Data type mapping

with FormatOptions.MapRules.Add do     // TIMESTAMP will be ftDateTime instead of ftTimeStamp
begin
   SourceDataType := dtDateTimeStamp;
   TargetDataType := dtDateTime;
end;
with FormatOptions.MapRules.Add do     // FLOAT will be ftFloat instead of ftSingle
begin
   SourceDataType := dtSingle;
   TargetDataType := dtDouble;
end;
FormatOptions.OwnMapRules := true;

在运行时,我创建了一个链接到TFDConnection的TFDQuery 我可以看到它继承了映射规则:FormatOptions.MapRules.count=2

我将一个INSERT查询分配给它的SQL.Text:

insert into TT_ACT (TT_ACT_ID,TT_PARENT_ID,TT_FROMDATE,TT_TODATE,TT_NAME,TT_NR,TT_CODE,TT_GROUP...)
values (:TT_ACT_ID,:TT_PARENT_ID,:TT_FROMDATE,:TT_TODATE,:TT_NAME,:TT_NR,:TT_CODE,:TT_GROUP,...)

这为params.count=42提供了数据类型为ftUnknown的参数(当然)。

然后我打电话给准备查询。

如果我现在检查已知的日期时间参数,我会看到params[x].datatype = ftTimeStamp,而不是ftDateTime。 因此,当查询返回数据库查看字段时,在设置参数时似乎不会侦听数据映射规则

这是一个错误吗?

在我的代码的后期阶段,这让我陷入困境,导致着名的338错误:

[FireDac][Phys][IB]-338 Param [TT_FROMDATE] type changed from [ftSQLTimeStamp] to [ftDateTime]. Query must be reprepared. 

我设法解决了这个错误,所以这不是问题的一部分。但我希望Params也遵循数据类型映射规则,这样可以使这一切变得更容易。

1 个答案:

答案 0 :(得分:1)

您只是错误定义了映射规则定义。对于参数,它是将目标转换为源。 Data Type Mapping主题也说明了这一点:

  

如果是命令参数,则规则定义a的转换   由应用程序指定的目标数据类型到源数据中   类型,由驱动程序支持。

所以要将命令参数从TIMESTAMP映射到dtDateTimeFLOAT再映射到dtDouble,只需在定义中使用目标交换源:

{ FLOAT → dtDouble in parameters }
with FormatOptions.MapRules.Add do
begin
  SourceDataType := dtDouble; { TFDParam.DataType }
  TargetDataType := dtSingle; { Firebird FLOAT }
end;
{ TIMESTAMP → dtDateTime in parameters }
with FormatOptions.MapRules.Add do
begin
  SourceDataType := dtDateTime; { TFDParam.DataType }
  TargetDataType := dtDateTimeStamp; { Firebird TIMESTAMP }
end;
{ enable custom map rules }
FormatOptions.OwnMapRules := True;

值得补充的是,参数的映射规则是唯一的。它们仅在准备命令时映射参数的数据类型(数据类型必须是可以确定的)。它们在传递给驱动程序时不会转换参数值。请考虑以下代码:

{ Firebird FLOAT equals to dtSingle data type, map it to dtDouble }
with FDQuery1.FormatOptions.MapRules.Add do
begin
  SourceDataType := dtDouble;
  TargetDataType := dtSingle;
end;
FDQuery1.FormatOptions.OwnMapRules := True;
{ setup the command; MyFloat field is Firebird FLOAT }
FDQuery1.SQL.Text := 'INSERT INTO MyTable (MyFloat) VALUES (:MyFloat)';
{ rules are applied when preparing command, so let's prepare it }
FDQuery1.Prepare;
{ now the parameter data type should be dtDouble instead of dtSingle }
if FDQuery1.ParamByName('MyFloat').DataType = dtDouble then
  ShowMessage('Parameter is of dtDouble data type');
{ but you can easily change the parameter data type to another, e.g. by mistake;
  this will change data type to dtSingle, so the whole mapping effort is lost }
FDQuery1.ParamByName('MyFloat').AsSingle := 1.2345;
{ if this would execute (which does not because the parameter data type has been
  changed since the command preparation), parameter map rules would still not be
  involved in converting parameter value for the driver }
FDQuery1.ExecSQL;

正如您所看到的,几乎没有任何努力(仅将确定的参数数据类型更改为另一个)。无论映射规则如何,参数值都会自动转换因此,即使您的参数数据类型与DBMS数据类型不匹配但是可以转换,FireDAC也会简单地为您转换它,无论如何(这种魔法在ConvertRawData方法内):

{ assume MyFloat FLOAT, MyTimeStamp TIMESTAMP in Firebird }
FDQuery1.SQL.Text := 'INSERT INTO MyTable (MyFloat, MyTimeStamp) VALUES (:MyFloat, :MyTimeStamp)';
{ setup parameter data types badly to be dtDouble and dtDateTime }
FDQuery1.ParamByName('MyFloat').AsFloat := 1.2345;
FDQuery1.ParamByName('MyTimeStamp').AsDateTime := Now;
{ and execute; parameter values will be converted automatically to DBMS data types
  dtDouble → dtSingle and dtDateTime → dtDateTimeStamp }
FDQuery1.ExecSQL;

所以即使在这里我也会重复,参数集合应该手动定义,而不是由DBMS从准备好的命令中定义(开发人员必须知道哪些值填充到哪些字段中)。