Beh, mi ci è voluto un po 'di tempo, ma ora funziona. Anche se sono ancora un po 'scettico riguardo alla mia soluzione fino ad ora (lo sto testando per il secondo giorno ora, finora non ho ancora avuto problemi). Sono anche contento di averlo funzionato per ora.
Devo scusarmi per la lunghezza di questa risposta, per la quale penso che non sia di beneficio per l'intera leggibilità del mio post, ma non vedo un'altra possibilità di fornire dettagli sufficienti su questo argomento.
Nel caso in cui altre persone stiano lavorando su cose simili, ho deciso di pubblicare la mia soluzione come risposta. Spero che sia d'aiuto, e ovviamente sono ansioso di sapere se mi sarebbe sfuggito qualcosa.
Ho scritto una funzione che tenta di aggiornare i metadati quando le differenze sono state trovate. Poiché i set di dati locali sono archiviati in formato XML (quindi tutto ciò che è memorizzato localmente può essere considerato una stringa), posso considerarli come varianti. Che in effetti è un enorme vantaggio quando si tratta di aggiungere dati:
procedure TdmDatabase.UpdateMetaDataFor(cds : TCustomClientDataSet; folder : String);
Ora seguono le procedure e la funzione nidificate. Questo potrebbe cambiare in seguito, dal momento che non sono ancora troppo sicuro di utilizzare questo approccio ...
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;
campi Aggiunta è la parte facile, soprattutto quando ci sono vincoli. I campi aggiuntivi vengono trovati dal set di dati remoto (in realtà proveniente dal database stesso) e i dati non sono importanti lì. Quindi, possiamo inserire un nuovo campo senza preoccuparci dei dati che devono essere inseriti lì. Se questo sarebbe il caso (per il nostro progetto non è necessario), questa funzione ha certo bisogno di aggiornamento:
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;
rimozione di campi è un po 'più specifico. Per prima cosa, deve ancora essere verificato se un campo che deve essere rimosso, ha dei vincoli. In tal caso, il metodo non dovrebbe continuare e l'intero set di dati locale con tutte le tabelle dovrebbe essere rimosso e ricostruito dal database, solo per garantire la corretta funzionalità. Attualmente, questi cambiamenti sono considerati importanti cambiamenti. Eseguo un controllo se sono state applicate importanti modifiche, se lo fa, molto probabilmente sarà necessaria anche una nuova versione dell'applicazione.
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;
La funzione seguente controlla l'uguaglianza tra i campi. Se vengono rilevate differenze per quanto riguarda DataType (FieldType), FieldName e così via, tenterà di aggiornarlo in base ai metadati del set di dati remoto, che è in testa.
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;
Questa è la funzione principale che cercherà di rilevare le differenze fra campi e campo definizioni dei set di dati locali e remoti.
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;
Poiché tutte le funzioni e le procedure utilizzate sopra sono piuttosto critico, ho deciso di farli annidati all'interno della procedura principale chiamata UpdateMetaDataFor. Potrei cambiarlo più tardi, ma per ora è abbastanza buono.
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;
Questo conclude la mia risposta molto esaustiva, che lascia ancora alcune cose a disposizione. Ad esempio, cosa si deve fare con i vincoli (questi non sono direttamente visibili su un set di dati locale).
Un altro approccio potrebbe essere quello di aggiungere una tabella al database stesso, che conterrà tutte le modifiche a seconda del numero di versione. Se questi cambiamenti sono considerati minori (cambio di nome per un campo che non ha vincoli o altro), allora questo approccio semi-automatico può ancora essere usato.
Come sempre, sono ancora molto curioso di altri approcci per garantire l'integrità di un database quando si applicano modelli di cartelle per database.
Solo con i nuovi metadati della struttura del database non è possibile decidere se una colonna viene rinominata o se una colonna precedente viene eliminata e ne viene aggiunta una nuova. –
Sono d'accordo, in tal caso continuo a scorrere i fielddefs di entrambi i set di dati per trovare le differenze. Questo è ancora un bel lavoro per il quale dubito che sia efficiente ... – RvdV79
Il database locale è di sola lettura? –