简体   繁体   中英

How can I save and restore the state of a Delphi component properly?

I am trying to save the state of a component and later restore it. I have made a little class helper to do so but the order of the way Delphi restores the class is causing a problem. It restores the Delimiter correctly but then sets RecordFormat which in turn over writes the Delimiter to be a semi colon. So I end up with the wrong value. Do you have any suggestions how I could get round this? I liked the simplicity of using a class helper but it will get convoluted if I have to handle it afterwards.

interface

type
  TFDBatchMoveTextReaderHelper = class helper for TFDBatchMoveTextReader
  private
    function GetState: String;
    procedure SetState( const Value: String );
  published
    property State: String read GetState write SetState;
  end;


implementation

function ComponentToStringProc( Component: TComponent ): string;
var
  BinStream: TMemoryStream;
  StrStream: TStringStream;
  s        : string;
begin
  BinStream := TMemoryStream.Create;
  try
    StrStream := TStringStream.Create( s );
    try
      BinStream.WriteComponent( Component );
      BinStream.Seek( 0, soFromBeginning );
      ObjectBinaryToText( BinStream, StrStream );
      StrStream.Seek( 0, soFromBeginning );
      Result := StrStream.DataString;
    finally
      StrStream.Free;
    end;
  finally
    BinStream.Free
  end;
end;

procedure StrtoComp( Value: string; AComponent: TComponent );
var
  StrStream: TStringStream;
  BinStream: TMemoryStream;
begin
  StrStream := TStringStream.Create( Value );
  try
    BinStream := TMemoryStream.Create;
    try
      ObjectTextToBinary( StrStream, BinStream );
      BinStream.Seek( 0, soFromBeginning );
      BinStream.ReadComponent( AComponent );
    finally
      BinStream.Free;
    end;
  finally
    StrStream.Free;
  end;
end;


{ TFDBatchMoveTextReaderHelper }

function TFDBatchMoveTextReaderHelper.GetState: String;
begin
  Result := ComponentToStringProc( Self );
end;

procedure TFDBatchMoveTextReaderHelper.SetState( const Value: String );
begin
  StrtoComp( Value, Self );
end;

initialization

RegisterClass( TFDBatchMoveTextReader );

end.

And here are my results (ignore the difference in the filename, I changed that before logging the state again. It's the DataDef values I am having problems with)

Text Compare
Produced: 27/03/2020 07:56:58

Mode:  All
Left file: Clipboard at 27/03/2020 07:54:09     Right file: Clipboard at 27/03/2020 07:54:18
AReaderState from the database = object TextReader: TFDBatchMoveTextReader <> lReader.State after applied to the object = object TextReader: TFDBatchMoveTextReader
  FileName = 'c:\molen\impexp\import\adm_loads_foynes.csv'                      FileName =
                                                                                  'C:\Users\stevesinclair\AppData\Local\Temp\96FFA4BD1C064E77972398' +
                                                                                  '7AD6E15495\adm_loads_foynes.csv'
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
  DataDef.Fields = <                                                       =    DataDef.Fields = <
    item                                                                          item
      FieldName = 'LOAD_ID'                                                         FieldName = 'LOAD_ID'
      DataType = atLongInt                                                          DataType = atLongInt
      FieldSize = 9                                                                 FieldSize = 9
    end                                                                           end
    item                                                                          item
      FieldName = 'SITE_ID'                                                         FieldName = 'SITE_ID'
      DataType = atLongInt                                                          DataType = atLongInt
      FieldSize = 5                                                                 FieldSize = 5
    end                                                                           end
    item                                                                          item
      FieldName = 'VEHICLE_REG'                                                     FieldName = 'VEHICLE_REG'
      DataType = atString                                                           DataType = atString
      FieldSize = 32                                                                FieldSize = 32
    end                                                                           end
    item                                                                          item
      FieldName = 'CREATED_BY'                                                      FieldName = 'CREATED_BY'
      DataType = atString                                                           DataType = atString
      FieldSize = 32                                                                FieldSize = 32
    end                                                                           end
    item                                                                          item
      FieldName = 'UPDATED_BY'                                                      FieldName = 'UPDATED_BY'
      DataType = atString                                                           DataType = atString
      FieldSize = 32                                                                FieldSize = 32
    end                                                                           end
    item                                                                          item
      FieldName = 'LDD_ID'                                                          FieldName = 'LDD_ID'
      DataType = atLongInt                                                          DataType = atLongInt
      FieldSize = 9                                                                 FieldSize = 9
    end                                                                           end
    item                                                                          item
      FieldName = 'TRANS_TYPE_ID'                                                   FieldName = 'TRANS_TYPE_ID'
      DataType = atLongInt                                                          DataType = atLongInt
      FieldSize = 3                                                                 FieldSize = 3
    end                                                                           end
    item                                                                          item
      FieldName = 'STAGE_ID'                                                        FieldName = 'STAGE_ID'
      DataType = atLongInt                                                          DataType = atLongInt
      FieldSize = 1                                                                 FieldSize = 1
    end                                                                           end
    item                                                                          item
      FieldName = 'ACCOUNT_ID'                                                      FieldName = 'ACCOUNT_ID'
      DataType = atLongInt                                                          DataType = atLongInt
      FieldSize = 9                                                                 FieldSize = 9
    end                                                                           end
    item                                                                          item
      FieldName = 'COMMODITY_ID'                                                    FieldName = 'COMMODITY_ID'
      DataType = atLongInt                                                          DataType = atLongInt
      FieldSize = 9                                                                 FieldSize = 9
    end                                                                           end
    item                                                                          item
      FieldName = 'FLN_ID'                                                          FieldName = 'FLN_ID'
      DataType = atLongInt                                                          DataType = atLongInt
      FieldSize = 9                                                                 FieldSize = 9
    end                                                                           end
    item                                                                          item
      FieldName = 'TLN_ID'                                                          FieldName = 'TLN_ID'
      DataType = atLongInt                                                          DataType = atLongInt
      FieldSize = 9                                                                 FieldSize = 9
    end                                                                           end
    item                                                                          item
      FieldName = 'NET_WEIGHT'                                                      FieldName = 'NET_WEIGHT'
      DataType = atLongInt                                                          DataType = atLongInt
      FieldSize = 5                                                                 FieldSize = 5
    end                                                                           end
    item                                                                          item
      FieldName = 'CREATED_DATE'                                                    FieldName = 'CREATED_DATE'
      DataType = atDateTime                                                         DataType = atDateTime
      FieldSize = 19                                                                FieldSize = 19
    end                                                                           end
    item                                                                          item
      FieldName = 'UPDATED_DATE'                                                    FieldName = 'UPDATED_DATE'
      DataType = atDate                                                             DataType = atDate
      FieldSize = 10                                                                FieldSize = 10
    end                                                                           end
    item                                                                          item
      FieldName = 'UPDATED_TIME'                                                    FieldName = 'UPDATED_TIME'
      DataType = atTime                                                             DataType = atTime
      FieldSize = 8                                                                 FieldSize = 8
    end                                                                           end
    item                                                                          item
      FieldName = 'TRAILER_REG'                                                     FieldName = 'TRAILER_REG'
      DataType = atString                                                           DataType = atString
      FieldSize = 6                                                                 FieldSize = 6
    end                                                                           end
    item                                                                          item
      FieldName = 'TRANSACTION_DATE'                                                FieldName = 'TRANSACTION_DATE'
      DataType = atDateTime                                                         DataType = atDateTime
      FieldSize = 19                                                                FieldSize = 19
    end                                                                           end
    item                                                                          item
      FieldName = 'IOX'                                                             FieldName = 'IOX'
      DataType = atString                                                           DataType = atString
      FieldSize = 1                                                                 FieldSize = 1
    end                                                                           end
    item                                                                          item
      FieldName = 'SHIP_ID'                                                         FieldName = 'SHIP_ID'
      DataType = atLongInt                                                          DataType = atLongInt
      FieldSize = 9                                                                 FieldSize = 9
    end>                                                                          end>
  DataDef.Delimiter = '"'                                                       DataDef.Delimiter = '"'
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
  DataDef.Separator = ','                                                  <>   DataDef.Separator = ';'
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
  DataDef.RecordFormat = rfCustom                                          =    DataDef.RecordFormat = rfCustom
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
  DataDef.FormatSettings.DateSeparator = '/'                               +-
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
  DataDef.FormatSettings.ShortDateFormat = 'DD/MM/YYYY'                    =    DataDef.FormatSettings.ShortDateFormat = 'DD/MM/YYYY'
  DataDef.FormatSettings.ShortTimeFormat = 'HH:mm:ss'                           DataDef.FormatSettings.ShortTimeFormat = 'HH:mm:ss'
  Left = 430                                                                    Left = 430
  Top = 20                                                                      Top = 20
end                                                                           end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------

Here is a test that I wrote for this which passes.

procedure TMyTestObject.TestReaderStaterfCustom;
var
  LOriginalReader: TFDBatchMoveTextReader;
  LRestoredReader: TFDBatchMoveTextReader;
const
  LDelimiter    = '|';
  LSeparator   = ',';
begin
  LOriginalReader := TFDBatchMoveTextReader.Create( nil );
  LRestoredReader := TFDBatchMoveTextReader.Create( nil );
  try
    LOriginalReader.DataDef.RecordFormat := rfCustom;
    LOriginalReader.DataDef.Delimiter    := LDelimiter;
    LOriginalReader.DataDef.Separator    := LSeparator;

    LRestoredReader.State := LOriginalReader.State;

    Assert.AreEqual( LOriginalReader.State, LRestoredReader.State, 'LOriginalReader.State, LRestoredReader.State (after setting)' );
    Assert.AreEqual( LOriginalReader.DataDef.Delimiter, LRestoredReader.DataDef.Delimiter );
    Assert.AreEqual( LOriginalReader.DataDef.Separator, LRestoredReader.DataDef.Separator );
    Assert.AreEqual( LDelimiter, LRestoredReader.DataDef.Delimiter );
    Assert.AreEqual( LSeparator, LRestoredReader.DataDef.Separator );

  finally
    LOriginalReader.Free;
    LRestoredReader.Free;
  end;

end;

But when I try to use it in my application it fails. I've put some logging round the setter as follows:

procedure TFDBatchMoveTextReaderHelper.SetState( const Value: String );
begin
  CodeSite.Send( 'TFDBatchMoveTextReaderHelper.SetState', Value );
  StrtoComp( Value, Self );
  CodeSite.Send( 'self.DataDef.Separator', self.DataDef.Separator );
end;

Which logs the separator as self.DataDef.Separator = ; which makes no sense as the value in the state string string is:

DataDef.Delimiter = '"'
DataDef.Separator = ','
DataDef.RecordFormat = rfCustom
DataDef.FormatSettings.DateSeparator = '/'
DataDef.FormatSettings.ShortDateFormat = 'DD/MM/YYYY'
DataDef.FormatSettings.ShortTimeFormat = 'HH:mm:ss'

And this is how I am actually using it in the application:

      lConn := TFDConnection.Create( nil );
  try
    lConn.ConnectionDefName := AConnectionDefName;
    lMover                  := TFDBatchMove.Create( lConn );
    lMemTable               := TFDMemTable.Create( lConn );
    lReader                 := TFDBatchMoveTextReader.Create( lConn );
    lReader.State           := AReaderState;
    lReader.FileName        := lTempFileName;
    lWriter                 := TFDBatchMoveDataSetWriter.Create( lConn );
    lWriter.Dataset         := lMemTable;
    lQuery                  := TFDQuery.Create( lConn );
    lQuery.Connection       := lConn;
    lQuery.SQL.Text         := ATargetSQL;
    try
      lQuery.Prepare;
    except
      on E: Exception do
      begin
        CodeSite.SendException( Format( 'Error perparing query: %s', [ ATargetSQL ] ), E );
        raise;
      end
    end;
    lMover.Reader := lReader;
    lMover.Writer := lWriter;

I have worked round this setting the separator and delimiter after applying the state string. In my rests I found that the order of how the state is saved was causing the problem. If DataDef.RecordFormat is before DataDef.Delimiter and DataDef.Separator it worked. However I have no control of how that is saved so I have hacked it when reading it back.

procedure TFDBatchMoveTextReaderHelper.SetState( const Value: String );
var
  RegEx: TRegEx;
  Match: TMatch;
const
  PatternRecordFormat = 'DataDef\.RecordFormat *= *(rfCustom)';
  PatternSeparator    = 'DataDef\.Separator *= *''(.)''';
  PatternDelimiter    = 'DataDef\.Delimiter *= *''(.)''';
begin
  StrtoComp( Value, Self );

  RegEx := TRegEx.Create( PatternRecordFormat, [ roIgnoreCase, roMultiLine ] );
  Match := RegEx.Match( Value );
  if Match.Success and ( Match.Groups.Count > 1 ) then
  begin
    RegEx := TRegEx.Create( PatternSeparator, [ roIgnoreCase, roMultiLine ] );
    Match := RegEx.Match( Value );
    if Match.Success and ( Match.Groups.Count > 1 ) then
      Self.DataDef.Separator := ( Match.Groups[ 1 ].Value + ',' )[ 1 ];

    RegEx := TRegEx.Create( PatternDelimiter, [ roIgnoreCase, roMultiLine ] );
    Match := RegEx.Match( Value );
    if Match.Success and ( Match.Groups.Count > 1 ) then
      Self.DataDef.Delimiter := ( Match.Groups[ 1 ].Value + '"' )[ 1 ];
  end;

end;

Not the best answer I know but it's all I could come up with. Here's my test to confirm it worked.

[ Test ]
[ TestCase( 'Comma Double Quote', 'rfCommaDoubleQuote' ) ]
[ TestCase( 'Semicolon Double Quote', 'rfSemicolonDoubleQuote' ) ]
[ TestCase( 'Tab Double Quote', 'rfTabDoubleQuote' ) ]
[ TestCase( 'Fixed Length', 'rfFixedLength' ) ]
[ TestCase( 'Field Per Line', 'rfFieldPerLine' ) ]
[ TestCase( 'Bar Double Quote', 'rfCustom' ) ]
procedure TestReaderState( const ARecordFormat: TFDTextRecordFormat );


procedure TMyTestObject.TestReaderState( const ARecordFormat: TFDTextRecordFormat );
var
  LOriginalReader: TFDBatchMoveTextReader;
  LRestoredReader: TFDBatchMoveTextReader;
  LDelimiter     : Char;
  LSeparator     : Char;
  LWithFieldNames: Boolean;
begin
  LOriginalReader := TFDBatchMoveTextReader.Create( nil );
  LRestoredReader := TFDBatchMoveTextReader.Create( nil );
  try
    case ARecordFormat of
      rfCommaDoubleQuote:
        begin
          LDelimiter := #34;
          LSeparator := #44;
        end;
      rfSemicolonDoubleQuote:
        begin
          LDelimiter := #34;
          LSeparator := #59;
        end;
      rfTabDoubleQuote:
        begin
          LDelimiter := #34;
          LSeparator := #9;
        end;
      rfFixedLength:
        begin
          LDelimiter      := #0;
          LSeparator      := #0;
          LWithFieldNames := False;
        end;
      rfFieldPerLine:
        begin
          LDelimiter      := #0;
          LSeparator      := #0;
          LWithFieldNames := False;
        end;
      rfCustom:
        begin
          LDelimiter := #124;
          LSeparator := #44;
        end;
    else
      begin
        LDelimiter := #34;
        LSeparator := #59;
      end;
    end;
    LOriginalReader.DataDef.RecordFormat := ARecordFormat;
    if LOriginalReader.DataDef.RecordFormat = ARecordFormat then
    begin
      LOriginalReader.DataDef.Delimiter := LDelimiter;
      LOriginalReader.DataDef.Separator := LSeparator;
    end;

    LRestoredReader.State := LOriginalReader.State;

    Assert.AreEqual( LOriginalReader.State, LRestoredReader.State, 'LOriginalReader.State, LRestoredReader.State (after setting)' );
    Assert.AreEqual( LOriginalReader.DataDef.Delimiter, LRestoredReader.DataDef.Delimiter );
    Assert.AreEqual( LOriginalReader.DataDef.Separator, LRestoredReader.DataDef.Separator );
    Assert.AreEqual( LDelimiter, LRestoredReader.DataDef.Delimiter );
    Assert.AreEqual( LSeparator, LRestoredReader.DataDef.Separator );
    Assert.AreEqual( LOriginalReader.DataDef.WithFieldNames, LRestoredReader.DataDef.WithFieldNames )

  finally
    LOriginalReader.Free;
    LRestoredReader.Free;
  end;

end;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM