2011年2月6日日曜日

Unified Interbaseコンポーネントをつかってみた(その4)

Unified Interbaseコンポーネントをつかってみた(その3)で、ClientDataSet接続用に
TUIBDataSetをカスタマイズした。このカスタマイズしたコンポーネントを使って
TClientDataSet及びTDataSetProviderを使ってのデータ更新を試してみた。

以下、その備忘録

DBExpressドライバを使えば、フラットなテーブルや簡単なリンクテーブルであれば
自動的にデータ操作のSQLを作ってDBに書き込んでくれる。

しかし、複雑なJoin等でデータを表示する場合はDbExpressドライバを使っても
テーブルへの操作は自前で実施する必要がある。

また、FlameRobin黒猫 SQL StudioA5:SQL Mk-2のツールでデータ更新用の
SQLである程度自動で作成できるので 自前で実施してもそんなに手間ではないので
データの更新を手動で行う。

DataSetProviderで、データの更新を自分で実施する方法は、エンバカデロさんのヘルプ
に手順が書いてあるのでこれに従って更新処理を書いた。

その実装は、以下のとおり

フォームのUIBTransactionコンポーネントを配置し、UIBDatabaseコンポーネントを
接続する。また今回はテストなので、暗黙のトランザクションになるようの
コンポーネントを設定した。(下図)





UIBQueryコンポーネントを配置し上記のUIBTransactionオブジェクトに接続する。

DataSetProviderのUpdateModeを実際の処理に合わせて設定する。
(今回は、"upWhereChanged"に設定)

DataSetProviderのBeforeUpdateRecordイベントハンドラにDB更新の処理を
記述する。このとき、更新処理が終わったら、

Applied := true

とし、ClientDataSetのキャッシュの更新終了状態にする。

今回のテストで書いた処理は下のとおり、
procedure TForm1.DataSetProvider1BeforeUpdateRecord(Sender: TObject;
  SourceDS: TDataSet; DeltaDS: TCustomClientDataSet; UpdateKind: TUpdateKind;
  var Applied: Boolean);
var
    i : Integer;
  SQL : String;
  ValueStr : String;
  NewStr : String;
  OldStr : String;
  //UIBDeltaDs : TUIBClientDataSet;
begin

  //UIBDeltaDs := TUIBClientDataSet.Create(Self);
  //UIBDeltaDs := DeltaDS.CloneCursor();

  UIBQuery1.SQL.Clear;

  UIBQuery1.SQL.Add('UPDATE EMPLOYEE SET ' + #13#10);

  while not(DeltaDS.eof) do
  begin
      //DeltaDS
     SQL := '';
     for i := 0 to DeltaDS.FieldCount - 1 do
     begin
       //UIBDeltaDs.DataConvert(
       if not(VarIsEmpty(DeltaDS.Fields[i].NewValue)) then
       begin
          //UIBQuery1.SQL.Add
          NewStr := VarToStr(DeltaDS.Fields[i].NewValue);
          OldStr := IfThen(not(VarIsNull(DeltaDs.Fields[i].OldValue)), VarToStr(DeltaDS.Fields[i].OldValue));

          if CompareText(NewStr,OldStr) <> 0 Then
          begin
             if (DeltaDs.Fields[i].DataType = ftDatetime) then
             begin
                ValueStr := FormatDateTime(
                                 'yyyy/mm/dd hh:nn:ss',
                                 VarToDateTime(DeltaDs.Fields[i].NewValue)
                            );
                   ValueStr := QuotedStr(ValueStr);
             end
             else
             begin
                       ValueStr := VarToStr(DeltaDS.Fields[i].NewValue);
                     if   (DeltaDs.Fields[i].DataType = ftString)
                       Or (DeltaDs.Fields[i].DataType = ftWideString)
                     then
                     begin
                        ValueStr := QuotedStr(ValueStr);
                     end
                end;
            end;

             SQL := SQL + ', ' + DeltaDS.Fields[i].FieldName + ' = ' + ValueStr + #13#10;
                 ListBox1.Items.Add(DeltaDS.Fields[i].FieldName);
                 ListBox1.Items.Add(NewStr);
                 ListBox1.Items.Add(OldStr);
          end;
       end;

     if Length(Trim(SQL)) > 0 then
     begin
       Sql := RightStr(Sql,Length(Sql)-1);
     end;

     UIBQuery1.SQL.Add(SQL);

     UIBQuery1.SQL.Add('WHERE EMP_NO = ' + VarToStr(DeltaDS.FieldByName('EMP_NO').OldValue));

     Memo1.Lines.Assign(UIBQuery1.SQL);

     UIBQuery1.ExecSQL;

      DeltaDS.Next;
  end;
    Applied := true;
  //UIBDeltaDs.Free;
end;

ここで、テーブルに対する操作は、UpdateKindで、変更対象のレコードは、DeltaDS
で取得できる。

あとは、適当なタイミングでClientDataSetのApplyUpdateメソッドをよびだせば、
データの更新ができる。(今回はボタンのクリックに割り当てた。)

以下、ソース例

procedure TForm1.Button1Click(Sender: TObject);
begin
  //ClientDataSet1.Post;
    UIBClientDataSet1.ApplyUpdates(-1);
end;

Unified Interbaseコンポーネントをつかってみた(その3)

Unified Interbaseコンポーネントをつかってみた(その1)でUIBDataSet経由でDBグリッドに
データを表示した。

しかし、UIBDataSetはReadOnlyのデータセットなので、編集が不可となっています。
(前回のサンプルを実行してもグリッドに入力ができません。)

いくつか、実験をおこなった結果、UIBDataSetは、単方向データセットとして機能している
ようなので、ClientDataSetを経由で接続すれば、DbExpressドライバのように使えるはず
だと思い試してみた。

以下、試して確認できたことを備忘録代わりに記述

フォームにTClientDataSetコンポーネントとTDataSetProviderコンポーネントを配置し、
UIBDataset → DatasetProvieder → ClientDataset → DataSourceにリンク変更


ここで、ClientDataset をActiveにするとタイムスタンプがうまく処理できないようでエラーが
発生するので、ClinetDataSet、およびUIBDataSetのソースを調べた結果、以下のとおり
データ形式の不整合があった。

UIBDataSet -> TDatetime型
ClinetDataSet → 通算のミリ秒(ftDatetime指定時)

そこで、ClientDatasetと時刻データが正しく連携が取れるようUIBDataSetを拡張したコンポーネントで、接続した。
以下、ソース

unit UIBCdsDataSet;

interface

uses
  SysUtils, Classes, DB, uibdataset;

type
  TUIBCdsDataSet = class(TUIBDataSet)
  private
    { Private 宣言 }
  protected
    { Protected 宣言 }
  public
    { Public 宣言 }
    function GetFieldData(FieldNo: Integer; Buffer: Pointer): Boolean; overload;{$IFNDEF FPC} override; {$ENDIF}
    {$IFNDEF FPC}
    function GetFieldData(Field: TField; Buffer: Pointer; NativeFormat: Boolean): Boolean; overload; override;
    {$ENDIF}
  published
    { Published 宣言 }
  end;

procedure Register;

implementation

uses uiblib;

procedure Register;
begin
  RegisterComponents('UIB', [TUIBCdsDataSet]);
end;

{ TUIBCdsDataSet }

function TUIBCdsDataSet.GetFieldData(FieldNo: Integer;
  Buffer: Pointer): Boolean;
var
  doubleBuf : TDateTime;
  aFieldType: TUIBFieldType;
  tsbuf : TTimeStamp;
begin

 Result := inherited GetFieldData(FieldNo, Buffer);
  if not(Result) then Exit;
  if Buffer = nil then Exit;

  aFieldType := Self.InternalFields.FieldType[FieldNo -1];

  if aFieldType = uftTimestamp then
  begin
   doubleBuf := TDateTime(Buffer^);
    tsbuf := DateTimeToTimeStamp(doubleBuf);

   Double(Buffer^) :=  TimeStampToMSecs(tsbuf);
 end;
end;

function TUIBCdsDataSet.GetFieldData(Field: TField; Buffer: Pointer;
  NativeFormat: Boolean): Boolean;
var
 //SF : TSQLResult;
  doubleBuf : TDateTime;
  aFieldType: TUIBFieldType;
  tsbuf : TTimeStamp;
begin

  Result := inherited GetFieldData(Field, Buffer,NativeFormat);
  if not(Result) then Exit;
  if Buffer = nil then Exit;

  //SF := Self.InternalFields;
  aFieldType := Self.InternalFields.FieldType[Field.FieldNo-1];

  if aFieldType = uftTimestamp then
  begin
   doubleBuf := TDateTime(Buffer^);
    tsbuf := DateTimeToTimeStamp(doubleBuf);

   Double(Buffer^) :=  TimeStampToMSecs(tsbuf);
  end;

end;

end.

これで時刻型のフィールドでエラーが発生することなく連携ができた。