【问题标题】:FireDAC mapping rules do not apply to parameters?FireDAC 映射规则不适用于参数?
【发布时间】:2017-09-26 13:13:19
【问题描述】:

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

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

我为其 SQL.Text 分配一个 INSERT 查询:

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 的参数(当然)。

然后我调用 Prepare 进行查询。

如果我现在检查一个已知的日期时间参数,我看到的是params[x].datatype = ftTimeStamp,而不是ftDateTime。 所以当查询回数据库查看字段时,在设置参数的时候似乎没有监听数据映射规则

这是一个错误吗?

在我的代码后期,这让我陷入了麻烦,导致了著名的 338 错误:

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

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

【问题讨论】:

    标签: delphi parameters firedac delphi-10.2-tokyo


    【解决方案1】:

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

    在命令参数的情况下,规则定义了一个 由应用程序指定的目标数据类型转换为源数据 类型,受驱动程序支持。

    所以要将命令参数从 TIMESTAMP 映射到 dtDateTimeFLOATdtDouble 只需在定义中将源与目标交换:

    { 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 从准备好的命令中定义(开发人员必须知道哪些值填充到哪些字段中)。

    【讨论】:

    • 哦,哇,太不方便了。当 查询 数据时,我的映射按预期工作(没有映射规则,FireBird TIMESTAMP 显示为 ftTimeStamp;规则为 ftDateTime)。如果我理解正确:如果我想映射 ptInput ParamType,我必须在相反方向覆盖查询的映射。
    • 但正如你在这里和in your other answer 所说,我将重写我的代码来自己构建参数集合。
    • 是的,它起作用了,因为您定义的是结果集字段的映射规则。我定义的是参数的映射规则。这有点误导,但仍然可读。对于参数,源是参数,目标 DBMS 字段。对于结果集字段,源是 DBMS 字段,目标结果集字段。如果要对结果集字段和参数使用映射规则,则需要为每个数据类型映射定义 2 条规则(用于两个方向)。
    • 如果您在分配参数值之前不准备查询,则根本不需要映射;)
    • @AplikmUj,正确。您可以准备一个查询来确定参数数据类型(应用映射的地方),或者分配您自己显式设置参数数据类型的值。我推荐后者。最终值转换(当命令执行时)不适用于映射规则(正如人们所期望的那样)。它们用于映射参数数据类型,而不是用于值转换。而参数数据类型只有你准备好命令才能确定。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多