只读TClientDataSet刷新因密钥违例而失败

时间:2017-11-22 11:58:46

标签: delphi firedac tclientdataset delphi-10.2-tokyo

我在下面的代码中获得了关于Refresh的密钥违规。

EmployeeContracts是一个TClientDataSet,通过TDataSetProvider加到TFDQuery与SQL:

select ec.*
from tt_emp e, tt_emp_contract ec
where (coalesce(e.tt_nonactive,0)=0)
and e.tt_emp_id = ec.tt_emp_id

代码片段:

with EmployeeContracts do
begin
  // Retrieve contracts of all active employees
  if (not Active) then
  begin
     Open;
  end;

  // Is record already correctly positioned?
  if (FieldByName(SEmpID).Asinteger=AEmpID) and
     (FieldByName(SFromDate).AsDateTime<=APeilDatum) and
     (FieldByName(SToDate).AsDateTime>=APeilDatum) then
  begin
     Result := True;
     Exit; 
  end;

  if not FindKey([AEmpID]) then  // Make sure the data are up to date. Refresh from the server.
  begin
     Refresh;  // ERROR HERE
  end;

  if FindKey([AEmpID]) then
  begin
     while (FieldByName(SempID).Asinteger=AEmpID) and (not EOF) do
     begin
        if (FieldByName(SFromDate).AsDateTime<=APeilDatum) and
           (FieldByName(SToDate).AsDateTime>=APeilDatum) then
        begin
           Result := True;
           Exit; 
        end;

        Next;
     end;
  end;
end;
  • IndexFieldNames为tt_emp_id;tt_fromdate
  • 我们之前已经完成了例程,clientdataset是开放的;只要FindKey返回true
  • 就没有错误
  • FetchOnDemand = true,但切换它没有区别
  • Delphi Tokyo Win32,FireBird 2.5.3,方言3数据库(实际上是GDB文件)
    ADDED 30-11-2017:我现在也在同一个应用程序的MSSQL数据库中获取此内容。
  • 如果我追踪Delphi代码,最后在TCustomClientDataSet.InternalRefresh调用FDSBase.AppendData时会发生错误。

当我们使用SQLDirect作为数据库访问层时,此代码有效,但不再使用FireBird。

可能是什么原因?

ADDED 1-12-2017它与UpdateOptions.RequestLive的{​​{1}}属性有关如果我将其默认真值切换为false,一切正常。

这一切都很奇怪。为什么RequestLive的默认值为true?
(为什么它的值实际上没有反映在DFM中,而是EnableDelete,EnableInsert,EnableUpdate切换)?

对于想要复制的人来说,这是完整的.pas源:
(它实际上有一个TFDConnection.TDataSource,但这些只是为了显示数据)

TDBGrid

这是完整的.dfm源:

unit uClientDatasetRefresh;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, FireDAC.Stan.Intf, FireDAC.Stan.Option,
  FireDAC.Stan.Param, FireDAC.Stan.Error, FireDAC.DatS, FireDAC.Phys.Intf,
  FireDAC.DApt.Intf, FireDAC.Stan.Async, FireDAC.DApt, FireDAC.UI.Intf,
  FireDAC.Stan.Def, FireDAC.Stan.Pool, FireDAC.Phys, FireDAC.Phys.FB,
  FireDAC.Phys.FBDef, FireDAC.VCLUI.Wait, Data.DB, Vcl.StdCtrls, Vcl.Grids,
  Vcl.DBGrids, Vcl.ExtCtrls, FireDAC.Comp.Client, FireDAC.Comp.DataSet,
  Datasnap.Provider, Datasnap.DBClient;

type
  TFrmClientDatasetRefresh = class(TForm)
    ClientDataSet1: TClientDataSet;
    DataSetProvider1: TDataSetProvider;
    FDQuery1: TFDQuery;
    FDConnection1: TFDConnection;
    Panel1: TPanel;
    DataSource1: TDataSource;
    DBGrid1: TDBGrid;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    function PositionOnEmployeeContractRecord(AEmpID: integer; ADate: TDateTime = 0): Boolean;
  public
  end;

var
  FrmClientDatasetRefresh: TFrmClientDatasetRefresh;

implementation

{$R *.dfm}

procedure TFrmClientDatasetRefresh.Button1Click(Sender: TObject);
begin
   PositionOnEmployeeContractRecord(20652);   // Has records in tt_emp_contract
   PositionOnEmployeeContractRecord(1024);    // Has no records in tt_emp_contract
end;

const
   SEmpID    = 'tt_emp_id';
   SFromDate = 'tt_fromdate';
   SToDate   = 'tt_todate';

function TFrmClientDatasetRefresh.PositionOnEmployeeContractRecord(AEmpID: integer; ADate: TDateTime = 0): Boolean;
begin
   Result := False;

   if (AEmpID=0) then Exit;
   if ADate=0 then ADate := Date;

   with ClientDataSet1 do
   begin
      if (not Active) then
      begin
         Open;
      end;

      if (FieldByName(SEmpID).Asinteger=AEmpID) and
         (FieldByName(SFromDate).AsDateTime<=ADate) and
         (FieldByName(SToDate).AsDateTime>=ADate) then
      begin
         Result := True;
         Exit;
      end;

      if not FindKey([AEmpID]) then
      begin
         Refresh;
      end;

      if FindKey([AEmpID]) then
      begin
         while (FieldByName(SempID).Asinteger=AEmpID) and (not EOF) do
         begin
            if (FieldByName(SFromDate).AsDateTime<=ADate) and
               (FieldByName(SToDate).AsDateTime>=ADate) then
            begin
               Result := True;
               Exit;
            end;

            Next;
         end;
      end;
   end;
end;

end.

tt_e​​mp的表结构很简单,只有两个记录,其整数object FrmClientDatasetRefresh: TFrmClientDatasetRefresh Left = 0 Top = 0 Caption = 'ClientDataset Refresh' ClientHeight = 276 ClientWidth = 560 Color = clBtnFace Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -11 Font.Name = 'Tahoma' Font.Style = [] OldCreateOrder = False Position = poScreenCenter PixelsPerInch = 96 TextHeight = 13 object Panel1: TPanel Left = 0 Top = 0 Width = 560 Height = 41 Align = alTop BevelOuter = bvNone TabOrder = 0 ExplicitLeft = 16 ExplicitTop = 8 ExplicitWidth = 185 object Button1: TButton Left = 32 Top = 8 Width = 75 Height = 25 Caption = 'Test' TabOrder = 0 OnClick = Button1Click end end object DBGrid1: TDBGrid Left = 0 Top = 41 Width = 560 Height = 235 Align = alClient DataSource = DataSource1 TabOrder = 1 TitleFont.Charset = DEFAULT_CHARSET TitleFont.Color = clWindowText TitleFont.Height = -11 TitleFont.Name = 'Tahoma' TitleFont.Style = [] end object ClientDataSet1: TClientDataSet Aggregates = <> IndexFieldNames = 'tt_emp_id;tt_fromdate' Params = <> ProviderName = 'DataSetProvider1' Left = 288 Top = 8 end object DataSetProvider1: TDataSetProvider DataSet = FDQuery1 Left = 376 Top = 8 end object FDQuery1: TFDQuery Connection = FDConnection1 SQL.Strings = ( 'select ec.*' 'from tt_emp e, tt_emp_contract ec' 'where (coalesce(e.tt_nonactive,0)=0)' 'and e.tt_emp_id = ec.tt_emp_id') Left = 448 Top = 8 end object FDConnection1: TFDConnection Params.Strings = ( 'DriverID=FB' 'Database=*****.GDB' 'Password=masterkey' 'User_Name=SYSDBA') LoginPrompt = False Left = 528 Top = 8 end object DataSource1: TDataSource DataSet = ClientDataSet1 Left = 216 Top = 8 end end 的值为20652,1024
tt_emp_id包含不同tt_emp_contract值的一些记录,包括20652,不包括1024.结构:

tt_emp_id

1 个答案:

答案 0 :(得分:4)

这就是发生的事情:

  1. 打开TClientDataSet使用TDataSetProvider填充它。
  2. 反过来,提供商会打开TFDQuery
  3. TFDQueryUpdateOptions.RequestLive设置为true,这会导致其获取元数据,尤其是每个ProviderFlags的{​​{1}}。
  4. FireDAC在TField ... select ...语句中检索主(第一个)表的唯一标识列,因此无法设置 tt_fromdate 作为&#34;识别&#34;的一部分键。
  5. 然后,客户端数据集将此元数据(&#34;标识&#34;密钥)传播到其内部 Midas 后端存储。
  6. 稍后,当调用from时,后端存储会使用此错误的密钥重新检查其存储记录的唯一性,并引发密钥违例异常。
  7. 引自online help

      

    TFDQuery TFDTable TFDMemTable TFDCommand 会自动检索唯一标识列(mkPrimaryKeyFields)当...包含在FetchOptions.Items中的fiMeta时,SELECT ... FROM ...语句中的main(第一个)表。
      ...
      当FireDAC无法正确确定它们时,应用程序可能需要明确指定唯一标识列。

    可能的解决方案:

    • Refresh组件中将RequestLive设置为false。将其设置为true的主要目的似乎是将FireDAC设置为automatically generate updating SQL commands,因此如果这是一个只读数据集,则可以将其禁用(如果您打算调用is also needed,请注意RefreshRecord 3}})。
    • 更改TFDQuery子句中的表顺序,因此 tt_e​​mp_contract 是第一个表,因此使用其主键。
    • from创建持久字段,并在与{em> tt_fromdate 对应的TFDQuery的{​​{1}}中设置pfInKey
    • ProviderFlags TField设置为 tt_e​​mp_id; tt_fromdate

    他们中的任何一个都必须完成这项工作。