Lar*_*tig 8 database delphi database-versioning
我正在研究许多Delphi应用程序,当发布新版本和用户选择安装其他模块时,需要在现场升级自己的数据库结构.应用程序使用各种嵌入式数据库(目前是DBISAM和Jet,但这可能会改变).
在过去,我使用DBISAM使用用户版本号完成此操作,而不是可以存储在每个表中.我发送了一组额外的空数据库文件,并在启动时使用FieldDefs比较每个表的版本号,以便在必要时更新已安装的表.虽然这有效但我发现必须运送数据库的备用副本并且更新版本的DBISAM已经改变了表重组方法,因此无论如何我都需要重写它.
我可以看到两种实现方法:使用数据库存储版本号并使用DDL脚本从旧版本获取更新版本或在应用程序内存储数据库结构的引用版本,比较启动时对数据库的引用up,并让应用程序生成DDL命令来升级数据库.
我想我可能要实现两者的一部分.我不希望每次应用程序启动时应用程序都将数据库与引用结构区分开来(太慢),因此我需要一个数据库结构版本号来检测用户是否使用了过时的结构.但是,我不确定我可以信任预先编写的脚本来进行结构升级,因为过去数据库可能已经部分更新,或者用户可能自己更改了数据库结构,所以我倾向于使用实际更新的参考差异.
研究这个问题我发现了几个数据库版本控制工具,但它们似乎都是针对SQL Server的,并且是在实际应用程序之外实现的.我正在寻找一个可以紧密集成到我的应用程序中的流程,它可以适应不同的数据库需求(我知道我必须编写适配器,自定义后代类或事件代码来处理各种DDL的差异数据库,这不会打扰我).
有没有人知道任何现成的东西,或者没有做到这一点,有没有人有任何想法:
在应用程序中存储通用关系数据库结构的引用版本的最佳方法.
将引用与实际数据库区分开来的最佳方法.
生成DDL以更新数据库的最佳方法.
类似的故事在这里 我们在"系统"表中存储数据库版本号,并在启动时检查它.(如果表/字段/值不存在,那么我们知道它是版本0,我们忘了添加那个位!)
在开发期间,当我们需要升级数据库时,我们编写了一个DDL脚本来完成工作,并且一旦开心它工作正常就可以将它作为文本资源添加到应用程序中.
当应用程序确定需要升级时,它会加载适当的资源并运行它/它们.如果需要升级多个版本,则必须按顺序运行每个脚本.事实证明最终只有几行代码.
主要的一点是,我们实际上不是直接编写DDL,而是使用基于GUI的工具以临时或"随机"方式修改表.这使得在时机成熟时更容易构建完整的升级脚本.并且不需要结构差异.
我在这里有一篇关于如何进行dbisam 数据库版本控制和sql server 的博客文章。
重要的部分是:
由于 dbisam 不支持视图,因此版本号(以及一堆其他信息)存储在数据库目录中的 ini 文件中。
我有一个数据模块,TdmodCheckDatabase。数据库中的每个表都有一个 TdbisamTable 组件。表组件包含表中的所有字段,并在表更改时更新。
为了进行数据库更改,使用了以下过程:
- 增加应用程序中的版本号
- 进行并测试数据库更改。
- 更新 TdmodCheckDatabase 中受影响的表
- 如果需要(很少),请向 TdmodCheckDatabase 添加进一步的升级查询。例如,设置新字段的值,或添加新的数据行。
- 使用提供的数据库工具生成 CreateDatabase 单元脚本。
- 更新单元测试以适应新的数据库
当应用程序运行时,会经历以下过程
- 如果没有找到数据库,则运行CreateDatabase单元,然后执行步骤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;
Run Code Online (Sandbox Code Playgroud)