使用Delphi在已安装的应用程序中进行数据库版
-
05-07-2019 - |
题
我正在研究许多Delphi应用程序,当发布新版本和用户选择安装其他模块时,需要在现场升级自己的数据库结构。应用程序使用各种嵌入式数据库(目前是DBISAM和Jet,但这可能会改变)。
过去我用DBISAM使用用户版本号完成此操作,而不是每个表都可以存储。我发送了一组额外的空数据库文件,并在启动时使用FieldDefs比较每个表的版本号,以便在必要时更新已安装的表。虽然这很有效,但我发现必须运送数据库的备用副本并且更新版本的DBISAM已经改变了表重组方法,因此无论如何我都需要重写它。
我可以看到两种实现方法:使用数据库存储版本号并使用DDL脚本从旧版本获取更新版本或在应用程序内存储数据库结构的参考版本,比较对数据库的引用在启动时,让应用程序生成DDL命令来升级数据库。
我认为我可能必须实现两者的一部分。我不希望每次应用程序启动时应用程序都将数据库与引用结构区分开来(太慢),因此我需要一个数据库结构版本号来检测用户是否使用了过时的结构。但是,我不确定我可以信任预先编写的脚本来进行结构升级,因为过去数据库可能已经部分更新,或者用户可能自己更改了数据库结构,所以我倾向于使用实际更新的参考差异。
研究这个问题我发现了一些数据库版本控制工具,但它们似乎都是针对SQL Server的,并且是在实际应用程序之外实现的。我正在寻找一个可以紧密集成到我的应用程序中的流程,它可以适应不同的数据库需求(我知道我必须编写适配器,自定义后代类或事件代码来处理各种DDL的差异数据库,这不会打扰我。)
有没有人知道任何现成的东西,或者没有这样做,有没有人有任何想法:
-
在应用程序中存储通用关系数据库结构的参考版本的最佳方法。
-
将引用与实际数据库区分开来的最佳方法。
-
生成DDL以更新数据库的最佳方法。
醇>
解决方案
我在这里有一篇关于如何 dbisam的博客文章数据库版本控制和 sql server 。
重要的部分是:
因为dbisam不支持视图, 版本号存储(沿着 在一个ini中的一堆其他信息) 文件在数据库目录中。
我有一个数据模块, TdmodCheckDatabase。这有一个 每个表的TdbisamTable组件 在数据库中。表组件 包含表格中的所有字段 每当表格更新时都会更新 改变。
要进行数据库更改,请执行以下操作: 使用了以下过程:
- 增加应用程序中的版本号
- 制作并测试数据库更改。
- 更新TdmodCheckDatabase
中受影响的表格- 如有必要(很少)添加进一步的升级查询 TdmodCheckDatabase。例如。设置 新字段的值,或添加新字段 数据行。
- 使用提供的数据库生成CreateDatabase单元脚本 工具。
- 更新单元测试以适应新的数据库
醇>当应用程序运行时,它就会运行 通过以下过程
- 如果未找到数据库,则运行CreateDatabase unit然后执行 第3步
- 从数据库ini文件中获取当前版本号
- 如果它小于预期的版本号那么 运行CreateDatabase(创建任何新表) 检查TdmodCheckDatabase中的每个表组件 应用任何表格更改 运行任何手动升级脚本
- 更新数据库ini文件中的版本号
醇>
代码示例
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;
其他提示
这里有类似的故事。 我们在“系统”表中存储数据库版本号,并在启动时检查它。 (如果表/字段/值不存在,那么我们知道它是版本0,我们忘记添加该位!)
在开发过程中,当我们需要升级数据库时,我们编写了一个DDL脚本来完成工作,并且很高兴它工作正常,它会作为文本资源添加到应用程序中。
当应用程序确定需要升级时,它会加载适当的资源并运行它/它们。如果需要升级多个版本,则必须按顺序运行每个脚本。事实证明最终只有几行代码。
主要的一点是,我们实际上不是直接编写DDL,而是使用基于GUI的工具以临时或“随机”方式修改表。这使得在时机成熟时更容易构建完整的升级脚本。并且不需要结构差异。
我正在为我的数据库使用ADO。我也使用版本号方案,但仅作为完整性检查。我有一个我开发的程序,它使用Connection.GetTableNames和Connection.GetFieldNames来识别与描述<!> quot; master <!>的XML文档的任何差异。数据库。如果存在差异,那么我构建适当的SQL来创建缺少的字段。我从不放弃其他的。
然后我有一个dbpatch表,其中包含一个由唯一名称标识的补丁列表。如果缺少特定修补程序,则应用它们并将相应的记录添加到dbpatch表中。通常这是新的存储过程,或字段大小调整,或索引
我还维护了一个min-db-version,由于我允许用户使用旧版本的客户端,因此我只允许他们使用版本<!> gt; = min-db-版本和<!> lt; = cur-db-version。
我所做的是在数据库中存储版本号,在应用程序中存储版本号。每次我需要更改数据库结构时,我都会创建一些代码来更新数据库的结构,并增加应用程序中的版本号。当应用程序启动时,它会比较,编号,如果需要运行一些代码来更新数据库结构 AND 更新数据库的版本号。因此,数据库现在与应用程序保持同步。我的代码就像
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只运行必要的代码,如:
procedure UpdateStructure(const aVersion : Integer);
begin
case aVersion of
1 : //some db code
2 : //some more db code
...
...
end;
UpdateDatabaseVersion(aVersion);
end;
您实际上可以使用相同的代码从头开始创建数据库
CreateDatabase;
for i := 1 to AppVersion do
UpdateStructure(i);