Datenbank Versionierung in installierten Anwendungen mit Delphi
-
05-07-2019 - |
Frage
Ich arbeite an einer Reihe von Delphi-Anwendungen, die müssen ihre eigenen Datenbankstrukturen im Bereich aktualisieren, wenn neue Versionen veröffentlicht werden und wenn Benutzer wählen, um zusätzliche Module zu installieren. Die Anwendungen sind eine Vielzahl von Embedded-Datenbanken (DBISAM und Jet zur Zeit, aber das kann sich ändern).
In der Vergangenheit habe ich dies getan, mit DBISAM die Benutzerversionsnummern verwenden, als mit jeder Tabelle gespeichert werden. Ich verschifft eine zusätzliche, leere Menge von Datenbank-Dateien und bei der Inbetriebnahme, verglichen die Versionsnummern der einzelnen Tabelle, die die FieldDefs mit der installierten Tabelle zu aktualisieren, falls erforderlich. Während dieses arbeitete, fand ich es ungeschickt eine Ersatzkopie der Datenbank und neueren Versionen von DBISAM transportieren müssen die Tabelle Umstrukturierungsmethodik geändert haben, so dass ich diese sowieso neu zu schreiben brauchen.
ich sehe zwei Wege zur Umsetzung dieses: eine Versionsnummer mit der Datenbank zu speichern und DDL-Skripten von älteren Versionen auf neuere Versionen zu erhalten oder um eine Referenzversion der Datenbankstruktur innerhalb der Anwendung zu speichern, die Referenz auf die Datenbank zu vergleichen bei der Inbetriebnahme, und mit der Anwendung generiert DDL die Datenbank aktualisieren Befehle.
Ich denke, dass ich wahrscheinlich Teile von beiden implementieren werden muß. Ich werde nicht die Anwendung will jedes Mal, wenn die Anwendung gestartet wird (zu langsam), um die Datenbank gegen die Referenzstruktur diff, so werde ich eine Datenbankstruktur Versionsnummer muß erkennen, ob der Benutzer eine veraltete Struktur verwendet. Allerdings bin ich nicht sicher, ob ich bereits geschriebene Skripte vertrauen kann die strukturelle Upgrade zu tun, wenn die Datenbank teilweise in der Vergangenheit aktualisiert worden sein könnte oder wenn der Benutzer sich geändert, um die Datenbankstruktur haben kann, so bin ich geneigt, eine zu verwenden, Referenz diff für die eigentliche Update.
Die Erforschung der Frage, die ich ein paar Datenbank Versionierung Tools gefunden haben, aber sie scheinen alle zu SQL Server gezielt und werden außerhalb der eigentlichen Anwendung implementiert. Ich bin für einen Prozess suchen, der in meine Anwendung integriert wird eng würde und dass auf unterschiedliche Datenbankanforderungen angepasst werden könnte (ich weiß, dass ich Adapter schreiben müssen würde, benutzerdefinierten abgeleiteten Klassen oder Ereigniscode Unterschiede in DDL Griff für verschiedene Datenbanken, das stört mich nicht).
Kennt jemand etwas aus dem Regal, die diese oder andernfalls der Fall ist, hat jemand irgendwelche Gedanken auf:
-
Der beste Weg, um eine Referenzversion einer allgemeinen relationalen Datenbankstruktur innerhalb einer Anwendung zu speichern.
-
Der beste Weg, um die Referenz gegen die eigentliche Datenbank Diff.
-
Die beste Art und Weise DDL zu erzeugen, um die Datenbank zu aktualisieren.
Lösung
Ich habe eine Blog-Post hier, wie ich dbisam Datenbank Versionierung und sQL Server .
Die wichtige Teile sind:
Da dbisam Ansichten nicht unterstützt, die Versionsnummer gespeichert ist (zusammen mit einem Haufen anderer info) in einem ini Datei im Datenbankverzeichnis.
Ich habe ein Datenmodul, TdmodCheckDatabase. Dies hat eine TdbisamTable Komponente für jede Tabelle in der Datenbank. Die Tabellenkomponente enthält alle Felder in der Tabelle und aktualisiert wird, wenn der Tisch geändert.
Um Datenbank Änderungen vornehmen, die folgende Verfahren verwendet wurde:
- Erhöhen Sie die Versionsnummer in der Anwendung
- Erstellen und Test DB Änderungen.
- Aktualisieren Sie die betroffenen Tabellen in TdmodCheckDatabase
- Bei Bedarf (selten) in weiteren Upgrade-Abfragen TdmodCheckDatabase. Z.B. setzen die Werte neuer Felder oder neue hinzufügen Datenzeilen.
- Erstellen Sie eine Createeinheit Skript die mitgelieferte Datenbank Werkzeuge.
- Update Unit-Tests der neuen db anpassen
Wenn die Anwendung ausgeführt wird, geht es durch den folgenden Prozess
- Wenn keine Datenbank gefunden wird, dann laufen Create Einheit und dann tun Schritt 3
- Erhalten Sie die aktuelle Versionsnummer aus der Datenbank ini-Datei
- Wenn es weniger als die erwarteten Versionsnummer dann Run Create (erstellen alle neuen Tabellen) Überprüfen Sie jede Tabellenkomponente in TdmodCheckDatabase Wenden Sie alle Tabellenänderungen laufen alle manuellen Upgrade-Skripte
- Aktualisieren Sie die Versionsnummer in der Datenbank ini-Datei
Ein Codebeispiel ist
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;
Andere Tipps
ähnliche Geschichte hier. Wir speichern eine DB-Versionsnummer in einer ‚System‘ Tabelle und prüfen, ob beim Start. (Falls die Tabelle / Feld / Wert nicht vorhanden ist, dann wissen wir, es ist Version 0, wo wir vergessen haben, dass etwas in hinzuzufügen!)
Während der Entwicklung, wie und wann wir brauchen die Datenbank aktualisieren wir einen DDL Skript schreiben, um die Arbeit zu tun, und einmal froh, dass es OK funktioniert es als Text-Ressource zu der App hinzugefügt wird.
Wenn die App feststellt, dass es zu aktualisieren braucht es die entsprechende Ressource lädt (n) und führt es / sie. Wenn es mehrere Versionen aktualisieren muss muss es jedes Skript ausführen, um. Es stellte sich heraus, um nur ein paar Zeilen Code in das Ende zu sein.
Der Haupt Punkt ist, dass anstelle die GUI-basierten Tool mit Hilfe von Tabellen in einer Ad-hoc oder ‚zufällig‘ Art und Weise zu ändern, schreiben wir tatsächlich die DDL sofort. Dies macht es viel einfacher, wenn die Zeit kommt, das vollständige Upgrade-Skript zu bauen. Und Struktur diff'ing ist nicht erforderlich.
Ich bin mit ADO für meine Datenbanken. Ich benutze auch eine Versionsnummer Schema, sondern nur als eine Plausibilitätsprüfung. Ich habe ein Programm, das ich entwickeln, das die Connection.GetTableNames und Connection.GetFieldNames verwendet eine Abweichung gegen ein XML-Dokument zu identifizieren, die die „Master“ Datenbank beschreibt. Wenn es eine Diskrepanz, dann baue ich die entsprechende SQL die fehlenden Felder zu erstellen. Ich lasse nie zusätzlich diejenigen.
Ich habe dann eine dbpatch Tabelle, die eine Liste von Patches durch einen eindeutigen Namen identifiziert enthält. Wenn es bestimmt Patches fehlt, dann werden sie angewandt und die entsprechende Datensatz wird die dbpatch Tabelle hinzugefügt. Meist geschieht dies ist neu gespeicherte Procs oder Feld Redimensionierung oder Indizes
Ich halte auch eine min-db-Version, die auch überprüft, da ich den Benutzern erlauben, eine ältere Version des Clients zu verwenden, lasse ich sie nur eine Version verwenden, das> ist = min-db-Version und <= cur-db-Version.
Was ich tue, ist in der Datenbank eine Versionsnummer speichern, und eine Versionsnummer in der Anwendung. Jedes Mal, ich brauche die Datenbankstruktur zu ändern, erstelle ich einige Code-Update, um die Struktur der Datenbank, und erhöhen Sie die Versionsnummer in der Anwendung. Wenn die Anwendung gestartet wird, vergleicht es, Zahlen und wenn läuft einige Code sein muss, um die Datenbankstruktur und aktualisiert die Datenbank der Versionsnummer zu aktualisieren. So ist die Datenbank nun mit der Anwendung auf dem Laufenden. Mein Code ist so etwas wie
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 läuft nur den notwendigen Code so etwas wie:
procedure UpdateStructure(const aVersion : Integer);
begin
case aVersion of
1 : //some db code
2 : //some more db code
...
...
end;
UpdateDatabaseVersion(aVersion);
end;
Sie können tatsächlich den gleichen Code verwenden, um die Datenbank von Grund auf neu erstellen
CreateDatabase;
for i := 1 to AppVersion do
UpdateStructure(i);