好吧,我花了很长时间,但我现在可以正常工作了。尽管到目前为止我仍然对我的解决方案持怀疑态度(我现在正在第二天对其进行测试,到目前为止仍然没有问题),但我也很高兴它现在可以工作。
我不得不为这个答案的长度道歉,我认为这对我的帖子的整个可读性没有好处,但我没有看到另一种可能性来提供关于这个主题的足够详细信息。
如果其他人正在研究类似的东西,我决定发布我的解决方案作为答案。我确实希望它有所帮助,当然我很想知道我是否可能错过了什么。
我写了一个函数,当发现差异时尝试更新元数据。由于本地数据集以 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;
我的非常全面的回答到此结束,仍然有一些事情可供考虑。例如,必须对约束做什么(这些在本地数据集上不直接可见)。
另一种方法是向数据库本身添加一个表,该表将包含取决于版本号的所有更改。如果这些更改被认为是次要的(没有约束的字段的名称更改或其他),那么仍然可以使用这种半自动方法。
与往常一样,我仍然对在为数据库应用公文包模型时确保数据库完整性的其他方法感到非常好奇。