2013-11-15 25 views
7

ハローコード愛好家!TClientDataSet:データベース構造を変更したときにローカルデータを保存して使用する方法

私は私のDelphi XE2の知識のいくつかの経験不足による間違いありません疑問を持っています。 ここで説明しようとします。

紹介:

私はデータでInterBaseデータベースを持っています。このデータベースはリモートマシン上にあります。 私が開発中のクライアントアプリケーションで、このデータベースを使用します。ネットワーク接続が利用できないときにアプリケーションを使用する必要があるため、私はブリーフケースモデルを使用する必要があります。そのため、データを取得してローカルにXML形式で格納するClientDataSetを使用しています。私の意見では、XMLファイルの代わりにローカルデータベースを使用する方が簡単ですが、まだ変更することはできません。したがって、私はまだXMLに縛られています:(

データの重要性のために、私はできるだけ安全にしておきたいと思います。他の開発者がデータベースの内部構造を変更している場合

私が今行っているのは、ClientDataSetを使用してデータベースからメタデータを取得することです。これはディスクに別に保存されています。フィールドの相違点を見つけたら、フィールド定義を構築して後でデータを追加するコードで新しいデータセットを作成します。言い換えれば、私は新しいローカルデータセットを作成します。これは、リモートデータベースからのテーブルの構造。私は、列(フィールド)削除や追加を見つけたとき

これは簡単です名前やフィールドのデータ型に変更があったとき、しかし、それは少し難しくなります。

私はまだ、しかし、私はこれが同様に行う必要があります感じることができる考慮し、主要外国とのユニークなキーを取ることはありません。

質問:

私の質問は主に、これは正しい方法であれば、私は疑問に思っていることです。これを達成するにはかなりの作業が必要ですが、このすべてを実装する前に、上記のことを達成するための他の(より便利で簡単な)方法があるかどうかを知りたいと思います。私はそれを見るように

、利用可能なデータは、ローカルに、リモート・データベース内に格納されたデータよりも高い優先度を有しています。ユーザーがローカルデータを使用していて、リモートデータを直接操作していないためです。

これについてのご意見はありますか?私は十分に私の質問を明確にすることができれば願っています。私は、まあ、それは私にかなり時間がかかりましたが、私はそれが今働いていInterbaseのXE(SP5)とDelphi XE 2

+2

データベース構造の新しいメタデータでのみ、列の名前が変更されたか古い列が削除され、新しい列が追加されたかを判断できません。 –

+0

私は同意します。その場合は、両方のデータセットのフィールド定義を繰り返して差分を探します。これはまだ効率的であるかどうか疑問に思っています。 – RvdV79

+0

ローカルデータベースは読み取り専用ですか? –

答えて

5

で働いています。私は今まで私の解決策についてまだ懐疑的ですが(私は2日目にそれをテストしています、それでもまだ問題はありません)、私は今でもそれを働かせて嬉しいです。

私はそれが私のポストの全体の読みやすさをメリットはありませんが、私はこのテーマについての十分な詳細を提供するために、別の可能性を見ていないと思われる私は、この答えの長さのために謝罪しなければなりません。

他の人が同じようなことに取り組んでいる場合は、解決策として答えを投稿することにしました。私はそれが助けてくれることを願っています。もちろん、私が何かを見逃していたかどうかを知りたいと思っています。

差分が見つかったときにメタデータを更新しようとする関数を記述しました。 ローカルデータセットはXML形式で格納されているので(ローカルに格納されたものはすべて文字列とみなすことができます)、それらをバリアントとして扱うことができます。データを追加するときに実際に大きな利点となるのは、次のとおりです。

procedure TdmDatabase.UpdateMetaDataFor(cds : TCustomClientDataSet; folder : String); 

次に、ネストされたプロシージャと関数が続きます。私はまだこのアプローチを利用についてあまりにもわからないので、これは、後から変更される場合があります...フィールドを追加

procedure AddInLocalData(local, newCds : TCustomClientDataSet); 
    var i : Integer; 
    begin 
     try 
     (* Assume that the new dataset is still closed... *) 
     newCds.CreateDataSet; 
     newCds.Insert; 
     for i := 0 to Pred(local.Fields.Count) do 
     begin 
      if (i < newCds.FieldCount) then 
      newCds.Fields[i].AsVariant := local.Fields[i].AsVariant; 
     end; 

     newCds.Post; 

     newCds.SaveToFile(folder + newCds.TableName + '_updated.xml', dfXMLUTF8); 
    except on E: Exception do 
     raise Exception.Create(_Translate(RS_ERROR_UNABLE_TO_SYNC_LOCAL_AND_REMOTE)); 
    end; 
    end; 

には制約がない場合は特に、簡単な部分です。追加のフィールド はリモートデータセット(実際にはデータベース自体から来ている)から見つけられ、データはそこでは重要ではありません。したがって、そこに挿入しなければならないデータを邪魔することなく、新しいフィールドを挿入することができます。そうであれば、この関数は必ず更新する必要があります(このプロジェクトでは必要ありません)。この関数は確実に更新する必要があります:フィールドの削除は、より具体的です。 1つは、削除が必要なフィールドに制約があるかどうかを確認する必要があります。その場合は、メソッドを続行しないでください。適切な機能を確保するために、すべてのテーブルを含むローカルデータセット全体をデータベースから削除して再構築する必要があります。現在、これらの変更は大きな変更と考えられています。私は大規模な変更が適用されているかどうかをチェックし、そうであればアプリケーションの新しいバージョンが必要になる可能性が最も高いでしょう。

function RemoveFieldsLocally(remote, local, newCds : TCustomClientDataSet) : TCustomClientDataSet; 
    var i  : Integer; 
     fieldDef : TFieldDef; 
     field : TField; 
    begin 
    try 
     (* Remote provider has lead here! *) 
     newCds.SetProvider(remote); 
     newCds.FieldDefs.Update; 

     (* Find the already existing fields and add them *) 
     for i := 0 to Pred(newCds.FieldDefs.Count) do 
     begin 
     field := newCds.FieldDefs[i].CreateField(cds); 

     if assigned(field) then 
     begin 
      field.FieldName := local.Fields[i].FieldName; 
      field.Calculated := local.Fields[i].Calculated; 
      field.Required := local.Fields[i].Required; 
      (* Necessary for compatibility with for example StringFields, BlobFields, etc *) 
      if (HasProperty(field, 'Size')) then 
      Field.Size := local.FIelds[i].Size; 
     end; 
     end; 

    (* Now add in the existing data from the local dataset. 
     Warning: since fields have been removed in the remote dataset, these 
     will not be added as well. If constraints were put up, these become 
     lost *) 
    AddInLocalData(local, newCds); 
    result := newCds; 
    except on E:Exception do 
    raise E; 
    end; 
end; 

以下の関数は、フィールド間の等しいかどうかをチェックします。 DataType(FieldType)、FieldNameなどの点で差異が検出された場合、リモートデータセットのメタデータに従って更新を試みます。

function VerifyInternalStructuresAndFields(remote, local, newCds : TCustomClientDataSet) : boolean; 
var i, equalityCounter : Integer; 
    equal : boolean; 
begin 
    try 
    (* We know that both datasets (local and remote) are equal for when it comes to 
     the fieldcount. In this case, the structure of the dataset from the remote  dataset is leading. *) 
    newCds.SetProvider(remote); 
    newCds.FieldDefs.Update; 

    equal := false; 
    equalityCounter := 0; 
    for i := 0 to Pred(newCds.FieldDefs.Count) do 
    begin 
     (* 1. Fielddefinitions which are exactly equal, can be copied *) 
     equal := (remote.Fields[i].FieldName = local.Fields[i].FieldName) and 
       (remote.Fields[i].Required = local.Fields[i].Required) and 
       (remote.Fields[i].Calculated = local.Fields[i].Calculated) and 
       (remote.Fields[i].DataType = local.Fields[i].DataType) and 
      (remote.Fields[i].Size = local.Fields[i].Size); 

     if (equal) then 
     begin 
     inc(equalityCounter); 
     with newCds.FieldDefs[i].CreateField(cds) do 
     begin 
      FieldName := local.Fields[i].FieldName; 
      Calculated := local.Fields[i].Calculated; 
      Required := local.Fields[i].Required; 
      Size := local.FIelds[i].Size; 
     end; 
     end 
     else (* fields differ, try to update it, here the remote fields are leading! *) 
     begin 
     if (MessageDlg(_Translate(RS_WARNING_DIFFERENCES_IN_FIELDS), mtWarning, mbYesNo, 0) = IDYES) then 
     begin 
      with newCds.FieldDefs[i].CreateField(cds) do 
      begin 
      FieldName := remote.Fields[i].FieldName; 
      Calculated := remote.Fields[i].Calculated; 
      Required := remote.Fields[i].Required; 
      if (HasProperty(remote, 'Size')) then 
       Size := remote.Fields[i].Size; 
      SetFieldType(remote.Fields[i].DataType); //TODO: If this turns out to be unnecessary, remove it. 
      end; 
     end 
     else 
     begin 
      result := false; 
      exit; 
     end; 
     end; 
    end; 

    if (equalityCounter = local.FieldCount) then 
    begin 
     result := false; 
    end else 
    begin 
     AddInLocalData(local, newCds); 
     result := true; 
    end; 
    except on E:Exception do 
    raise E; 
    end; 
end; 

これは、フィールドとリモートとローカルのデータセットのフィールド 定義間の差異を検出しようとする主な機能です。

function FindDifferencesInFields(remote, local: TCustomClientDataSet) : TCustomClientDataSet; 
var i, k  : Integer; 
    fieldDef : TFieldDef; 
    newCds : TKLAClientDataSet; 
begin 
    try 
    newCds := TCustomClientDataSet.Create(nil); 
    newCds.FileName := local.FileName; 
    newCds.Name := local.Name; 
    newCds.TableName := local.TableName; 

    (* First check if the remote dataset has added fields. *) 
    if (remote.FieldDefs.Count > local.FieldDefs.Count) then 
    begin 
     result := AddFieldsLocally(remote, local, newCds); 
    end 
    (* If no added fields could be found, check for removed fields *) 
    else if (remote.FieldDefs.Count < local.FieldDefs.Count) then 
    begin 
     result := RemoveFieldsLocally(remote, local, newCds); 
    end 
    (* Finally, check if the fieldcounts are equal and if renames have taken place *) 
    else if (remote.FieldDefs.Count = local.FieldDefs.Count) then 
    begin 
     if (VerifyInternalStructuresAndFields(remote, local, newCds)) then 
     result := newCds 
     else result := local; 
    end; 
    except on E:Exception do 
    raise Exception.Create('Could not verify remote and local dataset: ' + E.Message); 
    end; 
end; 

上記のすべてに使用する機能や手順は非常に重要なので、私は彼らがUpdateMetaDataForと呼ばれるメインプロシージャ内でネストすることにしました。私はこれを後で変えるかもしれませんが、今は十分です。

var fieldDefs : TFieldDefs; 
remotecds  : TCustomClientDataSet; 
constraints : TCheckConstraints; 
fileName  : String; 
k    : integer; 
begin 
    try 
    try 
     ConnectDB(false); 
     fileName := folder + cds.TableName + '_metadata_update.xml'; 

     (* Retrieve the latest metadata if applicable *) 
     remotecds := CreateDataset; 
     remotecds.SQLConnection := SQLConnection; 
     remotecds.TableName := cds.TableName; 
     remotecds.SQL.Text := Format('SELECT * from %s where id=-1', [cds.TableName]); 
     remotecds.Open; 

     remotecds.SaveToFile(fileName , dfXMLUTF8); 

     (* Load the local dataset with data for comparison *) 
     cds.LoadFromFile(folder + cds.FileName); 

     SyncProgress(_Translate(RS_SYNC_INTEGRITY_CHECK) + ' ' + cds.TableName); 

     cds := FindDifferencesInFields(remotecds, cds); 
     cds.SaveToFile(folder + cds.FileName, dfXMLUTF8); 
    except on E: Exception do 
     ShowMessage(E.Message); 
    end; 
finally 
    if assigned(remotecds) then 
    remotecds.Free; 
    if FileExists(fileName) then 
    SysUtils.DeleteFile(fileName); 
    end; 
end; 

これで私の非常に包括的な答えが終わりましたが、まだいくつか検討の余地があります。たとえば、制約を使用して何を行う必要があるか(これらはローカルデータセットでは直接表示されません)。

もう1つの方法として、データベースにテーブルを追加する方法があります。これには、バージョン番号に応じてすべての変更が含まれます。これらの変更が軽微(制約のないフィールドまたは名前が変更されたフィールドの名前変更)であると考えられる場合、この半自動アプローチを引き続き使用できます。

いつものように、私はデータベースのブリーフケースモデルを適用する際に、データベースのエンテリティを保証するための他のアプローチについてはまだ非常に興味があります。

関連する問題