8

我正在开发一些 Delphi 应用程序,当新版本发布和用户选择安装附加模块时,它们需要在现场升级自己的数据库结构。这些应用程序正在使用各种嵌入式数据库(目前 DISAM 和 Jet,但这可能会改变)。

在过去,我使用 DISAM 使用用户版本号来完成此操作,而不是可以存储在每个表中。我提供了一组额外的空数据库文件,并在启动时使用 FieldDefs 比较每个表的版本号,以便在必要时更新已安装的表。虽然这行得通,但我发现必须发送数据库的备用副本很笨拙,并且较新版本的 DBISAM 更改了表重组方法,因此无论如何我都需要重写它。

我可以看到两种实现方式:在数据库中存储版本号并使用 DDL 脚本从旧版本获取新版本,或者在应用程序中存储数据库结构的参考版本,在启动时比较对数据库的引用- up,并让应用程序生成 DDL 命令来升级数据库。

我认为我可能不得不实现两者的一部分。我不希望应用程序每次启动时都将数据库与参考结构进行比较(太慢),所以我需要一个数据库结构版本号来检测用户是否使用过时的结构。但是,当数据库过去可能已部分更新或用户可能自己更改了数据库结构时,我不确定我是否可以信任预先编写的脚本来进行结构升级,所以我倾向于使用实际更新的参考差异。

研究这个问题我发现了几个数据库版本控制工具,但它们似乎都针对 SQL Server 并且在实际应用程序之外实现。我正在寻找一个可以紧密集成到我的应用程序中并且可以适应不同数据库要求的进程(我知道我必须编写适配器、自定义后代类或事件代码来处理 DDL 中的各种差异数据库,这不会打扰我)。

有没有人知道现成的东西可以做到这一点或失败,有没有人有任何想法:

  1. 在应用程序中存储通用关系数据库结构的参考版本的最佳方式。

  2. 将参考与实际数据库进行比较的最佳方法。

  3. 生成 DDL 以更新数据库的最佳方式。

4

4 回答 4

5

类似的故事在这里。我们将数据库版本号存储在“系统”表中,并在启动时进行检查。(如果表/字段/值不存在,那么我们知道它是版本 0,我们忘记在其中添加该位!)

在开发过程中,当我们需要升级数据库时,我们编写了一个 DDL 脚本来完成这项工作,一旦它工作正常,它就会作为文本资源添加到应用程序中。

当应用程序确定它需要升级时,它会加载适当的资源并运行它/它们。如果它需要升级几个版本,它必须按顺序运行每个脚本。最终证明只有几行代码。

主要的一点是,我们实际上是直接编写 DDL,而不是使用基于 GUI 的工具以临时或“随机”方式修改表。这使得在时机成熟时构建完整的升级脚本变得更加容易。并且不需要结构差异。

于 2009-11-27T09:34:23.240 回答
2

我在这里有一篇关于我如何进行dbisam 数据库版本控制sql server的博客文章。

重要的部分是:

因为 dbisam 不支持视图,所以版本号(连同一堆其他信息)存储在数据库目录的一个 ini 文件中。

我有一个数据模块 TdmodCheckDatabase。这对数据库中的每个表都有一个 TdbisamTable 组件。表格组件包含表格中的所有字段,并在表格更改时更新。

为了进行数据库更改,使用了以下过程:

  1. 增加应用程序中的版本号
  2. 进行和测试数据库更改。
  3. 更新 TdmodCheckDatabase 中受影响的表
  4. 如有必要(很少)将进一步的升级查询添加到 TdmodCheckDatabase。例如设置新字段的值,或添加新的数据行。
  5. 使用提供的数据库工具生成 CreateDatabase 单元脚本。
  6. 更新单元测试以适应新数据库

当应用程序运行时,它会经历以下过程

  1. 如果没有找到数据库,则运行 CreateDatabase 单元,然后执行步骤 3
  2. 从数据库ini文件中获取当前版本号
  3. 如果它小于预期的版本号,则运行 CreateDatabase(以创建任何新表)检查 TdmodCheckDatabase 中的每个表组件应用任何表更改运行任何手动升级脚本
  4. 更新数据库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;
于 2009-11-27T00:10:34.347 回答
2

我正在为我的数据库使用 ADO。我也使用版本号方案,但仅作为完整性检查。我有一个我开发的程序,它使用 Connection.GetTableNames 和 Connection.GetFieldNames 来识别与描述“主”数据库的 XML 文档的任何差异。如果存在差异,那么我构建适当的 SQL 来创建缺失的字段。我从不放弃额外的。

然后我有一个 dbpatch 表,其中包含一个由唯一名称标识的补丁列表。如果缺少特定补丁,则应用它们并将适当的记录添加到 dbpatch 表中。大多数情况下,这是新的存储过程、字段大小调整或索引

我还维护了一个 min-db-version,因为我允许用户使用旧版本的客户端,所以也检查了它,我只允许他们使用 >= min-db-version 和 <= cur-db 的版本-版本。

于 2009-11-30T17:29:08.940 回答
1

我所做的是在数据库中存储一个版本号,在应用程序中存储一个版本号。每次我需要更改数据库结构时,我都会创建一些代码来更新数据库的结构,并在应用程序中增加版本号。当应用程序启动时,它会比较、编号,并在需要时运行一些代码来更新数据库结构更新数据库的版本号。因此,数据库现在与应用程序保持同步。我的代码类似于

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);
于 2009-11-26T21:54:28.187 回答