Controllo delle versioni del database nelle applicazioni installate utilizzando Delphi
-
05-07-2019 - |
Domanda
Sto lavorando su una serie di applicazioni Delphi che dovranno aggiornare le proprie strutture di database sul campo quando vengono rilasciate nuove versioni e quando gli utenti scelgono di installare moduli aggiuntivi. Le applicazioni utilizzano una varietà di database incorporati (DBISAM e Jet attualmente, ma questo potrebbe cambiare).
In passato l'ho fatto con DBISAM usando i numeri di versione dell'utente che possono essere memorizzati con ogni tabella. Ho spedito un set aggiuntivo e vuoto di file di database e, all'avvio, ho confrontato i numeri di versione di ogni tabella usando FieldDefs per aggiornare la tabella installata, se necessario. Mentre questo ha funzionato, ho trovato goffo dover spedire una copia di riserva del database e le versioni più recenti di DBISAM hanno cambiato la metodologia di ristrutturazione della tabella, quindi dovrò riscriverla comunque.
Posso vedere due modi per implementarlo: archiviare un numero di versione con il database e usare gli script DDL per passare da versioni precedenti a versioni più recenti o archiviare una versione di riferimento della struttura del database all'interno dell'applicazione, confrontando il riferimento al database all'avvio e facendo in modo che l'applicazione generi comandi DDL per aggiornare il database.
Penso che probabilmente dovrò implementare parti di entrambi. Non voglio che l'applicazione differisca il database dalla struttura di riferimento ogni volta che l'applicazione viene avviata (troppo lentamente), quindi avrò bisogno di un numero di versione della struttura del database per rilevare se l'utente sta utilizzando una struttura obsoleta. Tuttavia, non sono sicuro di potermi fidare degli script pre-scritti per eseguire l'aggiornamento strutturale quando il database potrebbe essere stato parzialmente aggiornato in passato o quando l'utente potrebbe aver modificato la struttura del database, quindi sono propenso a utilizzare un diff di riferimento per l'aggiornamento effettivo.
Ricerca della domanda Ho trovato un paio di strumenti per il controllo delle versioni del database, ma sembrano tutti indirizzati verso SQL Server e implementati al di fuori dell'applicazione reale. Sto cercando un processo che sarebbe strettamente integrato nella mia applicazione e che potrebbe essere adattato ai diversi requisiti del database (so che dovrò scrivere adattatori, classi discendenti personalizzate o codice evento per gestire le differenze in DDL per vari database, questo non mi disturba).
Qualcuno sa qualcosa che non lo fa o fallisce, qualcuno ha qualche idea su:
-
Il modo migliore per archiviare una versione di riferimento di una struttura di database relazionale generica all'interno di un'applicazione.
-
Il modo migliore per diffondere il riferimento rispetto al database effettivo.
-
Il modo migliore per generare DDL per aggiornare il database.
Soluzione
Ho un post sul blog qui su come faccio dbisam versione del database e sql server .
Le parti importanti sono:
Poiché dbisam non supporta le visualizzazioni, il numero di versione è memorizzato (insieme con un sacco di altre informazioni) in un ini file nella directory del database.
Ho un modulo dati, TdmodCheckDatabase. Questo ha un Componente TdbisamTable per ogni tabella nel database. Il componente della tabella contiene tutti i campi nella tabella e viene aggiornato ogni volta che la tabella è cambiato.
Per apportare modifiche al database, il è stato utilizzato il seguente processo:
- Aumenta il numero di versione nell'applicazione
- Apporta e verifica le modifiche al DB.
- Aggiorna le tabelle interessate in TdmodCheckDatabase
- Se necessario (raramente) aggiungi ulteriori query di aggiornamento a TdmodCheckDatabase. Per esempio. per impostare il valori di nuovi campi o per aggiungere nuovi righe di dati.
- Genera uno script di unità CreateDatabase utilizzando il database fornito strumenti.
- Aggiorna i test unitari per adattarli al nuovo db
Quando l'applicazione viene eseguita, va bene attraverso la seguente procedura
- Se non viene trovato alcun database, eseguire l'unità CreateDatabase e quindi fare passaggio 3
- Ottieni il numero di versione corrente dal file ini del database
- Se è inferiore al numero di versione previsto, allora Esegui CreateDatabase (per creare nuove tabelle) Controllare tutti i componenti della tabella in TdmodCheckDatabase Applica eventuali modifiche alla tabella eseguire eventuali script di aggiornamento manuale
- Aggiorna il numero di versione nel file ini del database
Un esempio di codice è
class procedure TdmodCheckDatabase.UpgradeDatabase(databasePath: string; currentVersion, newVersion: integer);
var
module: TdmodCheckDatabase;
f: integer;
begin
module:= TdmodCheckDatabase.create(nil);
try
module.OpenDatabase( databasePath );
for f:= 0 to module.ComponentCount -1 do
begin
if module.Components[f] is TDBISAMTable then
begin
try
// if we need to upgrade table to dbisam 4
if currentVersion <= DB_VERSION_FOR_DBISAM4 then
TDBISAMTable(module.Components[f]).UpgradeTable;
module.UpgradeTable(TDBISAMTable(module.Components[f]));
except
// logging and error stuff removed
end;
end;
end;
for f:= currentVersion + 1 to newVersion do
module.RunUpgradeScripts(f);
module.sqlMakeIndexes.ExecSQL; // have to create additional indexes manually
finally
module.DBISAMDatabase1.Close;
module.free;
end;
end;
procedure TdmodCheckDatabase.UpgradeTable(table: TDBISAMTable);
var
fieldIndex: integer;
needsRestructure: boolean;
canonical: TField;
begin
needsRestructure:= false;
table.FieldDefs.Update;
// add any new fields to the FieldDefs
if table.FieldDefs.Count < table.FieldCount then
begin
for fieldIndex := table.FieldDefs.Count to table.Fields.Count -1 do
begin
table.FieldDefs.Add(fieldIndex + 1, table.Fields[fieldIndex].FieldName, table.Fields[fieldIndex].DataType, table.Fields[fieldIndex].Size, table.Fields[fieldIndex].Required);
end;
needsRestructure:= true;
end;
// make sure we have correct size for string fields
for fieldIndex := 0 to table.FieldDefs.Count -1 do
begin
if (table.FieldDefs[fieldIndex].DataType = ftString) then
begin
canonical:= table.FindField(table.FieldDefs[fieldIndex].Name);
if assigned(canonical) and (table.FieldDefs[fieldIndex].Size <> canonical.Size) then
begin
// field size has changed
needsRestructure:= true;
table.FieldDefs[fieldIndex].Size:= canonical.Size;
end;
end;
end;
if needsRestructure then
table.AlterTable(); // upgrades table using the new FieldDef values
end;
procedure TdmodCheckDatabase.RunUpgradeScripts(newVersion: integer);
begin
case newVersion of
3: sqlVersion3.ExecSQL;
9: sqlVersion9.ExecSQL;
11: begin // change to DBISAM 4
sqlVersion11a.ExecSQL;
sqlVersion11b.ExecSQL;
sqlVersion11c.ExecSQL;
sqlVersion11d.ExecSQL;
sqlVersion11e.ExecSQL;
end;
19: sqlVersion19.ExecSQL;
20: sqlVersion20.ExecSQL;
end;
end;
Altri suggerimenti
Storia simile qui. Memorizziamo un numero di versione del DB in una tabella "di sistema" e lo controlliamo all'avvio. (Se la tabella / campo / valore non esiste, allora sappiamo che è la versione 0 in cui ci siamo dimenticati di aggiungere quel bit!)
Durante lo sviluppo come e quando abbiamo bisogno di aggiornare il database, scriviamo uno script DDL per fare il lavoro e una volta felici che funzioni OK, viene aggiunto come risorsa di testo all'app.
Quando l'app determina che deve essere aggiornato, carica le risorse appropriate e le esegue. Se è necessario aggiornare più versioni, è necessario eseguire ogni script in ordine. Alla fine risulta essere solo poche righe di codice.
Il punto principale è che invece di utilizzare gli strumenti basati sulla GUI per modificare le tabelle in modo ad-hoc o 'casuale', in realtà scriviamo subito il DDL. Ciò rende molto più semplice, quando arriva il momento, creare lo script di aggiornamento completo. E non è richiesto il diffing della struttura.
Sto usando ADO per i miei database. Uso anche uno schema del numero di versione, ma solo come controllo di integrità. Ho un programma che ho sviluppato che utilizza Connection.GetTableNames e Connection.GetFieldNames per identificare eventuali discrepanze rispetto a un documento XML che descrive il & Quot; master & Quot; Banca dati. Se c'è una discrepanza, quindi creo l'SQL appropriato per creare i campi mancanti. Non ne butto mai altri.
Ho quindi una tabella dbpatch, che contiene un elenco di patch identificate da un nome univoco. Se mancano patch specifiche, vengono applicate e il record appropriato viene aggiunto alla tabella dbpatch. Molto spesso si tratta di nuovi proc memorizzati, ridimensionamento del campo o indici
Mantengo anche una versione min-db, che è anche verificata poiché consento agli utenti di utilizzare una versione precedente del client, consento loro solo di utilizzare una versione > = min-db- versione e < = cur-db-version.
Quello che faccio è memorizzare un numero di versione nel database e un numero di versione nell'applicazione. Ogni volta che devo modificare la struttura del database, creo un po 'di codice per aggiornare la struttura del database e aumentare il numero di versione nell'applicazione. All'avvio dell'applicazione, confronta i numeri e, se necessario, esegue un codice per aggiornare la struttura del database AND aggiorna il numero di versione del database. Pertanto, il database è ora aggiornato con l'applicazione. Il mio codice è simile a
if DBVersion < AppVersion then
begin
for i := DBVersion+1 to AppVersion do
UpdateStructure(i);
end
else
if DBVersion > AppVersion then
raise EWrongVersion.Create('Wrong application for this database');
UpdateStructure esegue semplicemente il codice necessario in qualcosa del tipo:
procedure UpdateStructure(const aVersion : Integer);
begin
case aVersion of
1 : //some db code
2 : //some more db code
...
...
end;
UpdateDatabaseVersion(aVersion);
end;
Puoi effettivamente usare lo stesso codice per creare il database da zero
CreateDatabase;
for i := 1 to AppVersion do
UpdateStructure(i);