gpt4 book ai didi

database - TClientDataSet:如何在数据库结构发生变化时保持本地数据的保存和可用

转载 作者:太空狗 更新时间:2023-10-30 01:47:28 28 4
gpt4 key购买 nike

你好代码爱好者!

我有一个问题,这无疑是由于我对 Delphi XE2 知识缺乏经验。我会在这里尝试解释一下。

简介:

我有一个包含数据的 Interbase 数据库。该数据库位于远程机器上。我正在开发的客户端应用程序使用这个数据库。由于应用程序必须在没有可用网络连接时使用,所以我必须使用公文包模型。这就是我使用 ClientDataSet 检索数据并以 XML 格式在本地存储数据的原因。在我看来,使用本地数据库而不是 XML 文件会更容易,但我还不允许更改它。因此我仍然受制于 XML :(

由于数据的重要性,我想尽可能地保证它的安全。即使其他开发人员正在更改数据库的内部结构(例如,添加、重命名甚至删除表中的字段),本地存储的数据仍然必须可用。

我现在所做的是使用 ClientDataSet 从数据库中检索元数据。这是单独存储在磁盘上的。我打算做的下一件事是将数据库中的元数据与存储在本地数据集中的元数据进行比较。当我发现字段中存在差异时,我会在代码中创建一个新数据集,在其中构建字段定义并随后添加数据。换句话说,我只是创建了一个新的本地数据集,它符合远程数据库中表的结构。

当我发现列(字段)被删除或添加时,这很容易,但是当名称或字段的数据类型发生变化时,这会变得有点困难。

我还没有考虑主键、外键和唯一键,但我觉得也必须这样做。

问题:

我的问题主要是,我想知道这是否是正确的方法。实现这一点需要做很多工作,在我开始实现这一切之前,我想知道是否有其他(更方便和更容易)的方法来实现我上面描述的事情。

在我看来,本地可用的数据比存储在远程数据库中的数据具有更高的优先级。只是因为用户正在使用本地数据而不是直接使用远程数据。

对此有什么想法吗?我希望我能充分澄清我的问题,如果没有,请提问,我会提供更多详细信息。我正在使用 Interbase XE (SP5) 和 Delphi XE 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;

添加字段是比较容易的部分,尤其是在没有约束的情况下。额外的字段是从远程数据集中找到的(实际上来自数据库本身)并且数据在那里并不重要。因此,我们可以插入一个新字段而不必担心必须在那里插入的数据。如果是这种情况(对于我们的项目来说没有必要),那么这个函数肯定需要更新:

   function AddFieldsLocally( remote, local, newCds : TCustomClientDataSet ) :  TCustomClientDataSet;
var i : Integer;
fieldDef : TFieldDef;
begin
try
(* Local provider is leading... *)
newCds.SetProvider(local);
newCds.FieldDefs.Update;
(* Find the already existing fields and add them *)
for i := 0 to Pred(newCds.FieldDefs.Count) do
begin
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; //TODO: Add checking here!
end;
end;
(* Check for additional fields that exist remotely and add them *)
for i := newCds.fieldDefs.Count to Pred(remote.FieldDefs.Count) do
begin
fieldDef := remote.FieldDefs.Items[i];
if (fieldDef <> nil) then
begin
newCds.FieldDefs.Add(fieldDef.Name, fieldDef.DataType, fieldDef.Size, fieldDef.Required);
newCds.FieldDefs[ Pred( newCds.FieldDefs.Count )].CreateField(newCds);
end;
end;

(* Finally, add the existing local data to the newly created dataset *)
AddInLocalData(local, newCds);
result := newCds;
except on E:Exception
raise E;
end;
end;

删除字段更具体一些。一方面,仍然需要验证需要删除的字段是否有约束。如果是这样,则方法不应继续,并且应删除包含所有表的整个本地数据集并从数据库中重建,以确保正常运行。目前,这些变化被认为是重大变化。我检查是否应用了重大更改,如果应用了,则很可能还需要应用程序的新版本。

  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;

我的非常全面的回答到此结束,其中仍有一些问题需要考虑。例如,必须用约束做什么(这些在本地数据集上不直接可见)。

另一种方法可能是向数据库本身添加一个表,该表将根据版本号包含所有更改。如果这些更改被认为是次要更改(没有约束或任何其他字段的名称更改),则仍然可以使用这种半自动方法。

一如既往,我仍然很好奇在为数据库应用公文包模型时确保数据库完整性的其他方法。

关于database - TClientDataSet:如何在数据库结构发生变化时保持本地数据的保存和可用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19997268/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com