Rvd*_*V79 7 database delphi interbase tclientdataset
您好代码爱好者!
我有一个问题,这无疑是由于我的Delphi XE2知识缺乏经验.我会试着在这里解释一下.
介绍:
我有一个带数据的Interbase数据库.该数据库位于远程计算机上.我正在开发的客户端应用程序使用此数据库.由于必须在没有可用的网络连接时使用该应用程序,我必须使用公文包模型.这就是为什么我使用ClientDataSet来检索数据并以XML格式本地存储它.在我看来,使用本地数据库代替XML文件会更容易,但我还是不允许改变它.因此我仍然绑定到XML :(
由于数据的重要性,我希望尽可能保持安全.即使其他开发人员正在更改数据库的内部结构(例如,表中的字段被添加,重命名甚至删除),本地存储的数据仍然必须可用.
我现在做的是,我使用ClientDataSet从数据库中检索元数据.它分别存储在磁盘上.我计划做的下一件事是将数据库中的元数据与存储在本地数据集中的元数据进行比较.当我在字段中发现差异时,我在代码中创建了一个新的数据集,我建立了字段定义并在之后添加数据.换句话说,我只是创建一个新的本地数据集,它符合远程数据库中表的结构.
当我找到列(字段)删除或添加时,这很容易,但是当字段名称或数据类型发生更改时,它会变得有点困难.
我还没有考虑主键,外键和唯一键,但我也觉得必须这样做.
题:
我的问题主要是,我想知道这是否是正确的方法.实现这一点是相当有意义的,在我开始实现这一切之前,我想知道是否还有其他(更方便,更简单)的方法来实现我上面描述的内容.
在我看来,本地可用的数据优先于远程数据库中存储的数据.仅仅因为用户正在处理本地数据而不是直接处理远程数据.
有什么想法吗?我希望我能够充分澄清我的问题,如果没有,请询问,我会提供更多细节.我正在使用Interbase XE(SP5)和Delphi XE 2.
好吧,我花了很长时间,但我现在有工作.虽然到目前为止我对我的解决方案仍然有点怀疑(我现在正在测试它,但到目前为止仍然没有问题)我现在也很高兴让它工作了.
我必须为这个答案的长度道歉,我认为这对我的帖子的整个可读性没有好处,但我没有看到另一种可能提供关于这个主题的足够细节.
如果其他人正在研究类似的东西,我决定发布我的解决方案作为答案.我希望它有所帮助,当然我很想知道我是否可能错过了什么.
我写了一个函数,尝试在找到差异时更新元数据.由于本地数据集以XML格式存储(因此本地存储的所有内容都可以被视为字符串),我可以将它们视为变体.在添加数据时,这实际上是一个巨大的好处:
procedure TdmDatabase.UpdateMetaDataFor( cds : TCustomClientDataSet; folder : String);
Run Code Online (Sandbox Code Playgroud)
现在嵌套的过程和函数如下.这可能会在以后改变,因为我仍然不太确定使用这种方法......
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;
Run Code Online (Sandbox Code Playgroud)
添加字段很容易,尤其是在没有约束的情况下.额外的字段可以从远程数据集中找到(实际上来自数据库本身),数据在那里并不重要.因此,我们可以在不打扰必须插入的数据的情况下插入新字段.如果是这种情况(对于我们的项目没有必要),那么这个功能肯定需要更新:
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;
Run Code Online (Sandbox Code Playgroud)
删除字段更具体一些.首先,如果需要删除的字段有限制,仍需要进行验证.如果是,则方法不应该继续,并且应该从数据库中删除并重建包含所有表的整个本地数据集,以确保正确的功能.目前,这些变化被认为是重大变化.我检查是否应用了主要更改,如果有,则很可能还需要新版本的应用程序.
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;
Run Code Online (Sandbox Code Playgroud)
下面的函数检查字段之间的相等性.如果在涉及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;
Run Code Online (Sandbox Code Playgroud)
这是主要功能,它将尝试检测远程和本地数据集的字段和字段定义之间的差异.
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;
Run Code Online (Sandbox Code Playgroud)
由于上面使用的所有函数和过程都非常关键,我决定将它们嵌套在名为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;
Run Code Online (Sandbox Code Playgroud)
这总结了我非常全面的答案,这仍然有些事情需要考虑.例如,必须使用约束(这些在本地数据集上不直接可见).
另一种方法可能是向数据库本身添加一个表,该表将包含所有更改,具体取决于版本号.如果这些更改被认为是次要的(对于没有约束或任何约束的字段的名称更改),则仍然可以使用此半自动方法.
与往常一样,我仍然非常好奇在应用数据库公文包模型时确保数据库完整性的其他方法.