Database 使用Delphi在已安装的应用程序中进行数据库版本控制

Database 使用Delphi在已安装的应用程序中进行数据库版本控制,database,delphi,database-versioning,Database,Delphi,Database Versioning,我正在开发一些Delphi应用程序,当新版本发布时,以及当用户选择安装其他模块时,这些应用程序需要升级自己的数据库结构。这些应用程序正在使用各种嵌入式数据库(DBISAM和Jet目前正在使用,但这种情况可能会改变) 在过去,我使用DBISAM完成了这项工作,使用的是用户版本号,而不是每个表可以存储的版本号。我提供了一组额外的空数据库文件,并在启动时使用FieldDefs比较每个表的版本号,以便在必要时更新已安装的表。虽然这样做有效,但我发现必须提供数据库的备用副本是很笨拙的,而且DBISAM的较

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

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

我可以看到两种实现方法:在数据库中存储版本号,使用DDL脚本从旧版本到新版本,或者在应用程序中存储数据库结构的引用版本,在启动时将引用与数据库进行比较,让应用程序生成DDL命令来升级数据库

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

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

有没有人知道有什么现成的东西能做到这一点或是不能做到这一点,有没有人想到:

  • 在应用程序中存储通用关系数据库结构的引用版本的最佳方法

  • 将引用与实际数据库区分开来的最佳方法

  • 生成DDL以更新数据库的最佳方法


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

    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');
    
    实际上,您可以使用相同的代码从头开始创建数据库

    CreateDatabase;
    for i := 1 to AppVersion do
      UpdateStructure(i);
    

    我在这里有一篇关于我如何做和如何做的博客文章

    重要的部分是:

    因为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;
    
    类过程TdmodCheckDatabase.UpgradeDatabase(databasePath:string;currentVersion,newVersion:integer);
    变量
    模块:TdmodCheckDatabase;
    f:整数;
    开始
    模块:=TdmodCheckDatabase.create(nil);
    尝试
    OpenDatabase模块(databasePath);
    对于f:=0到module.ComponentCount-1 do
    开始
    如果模块组件[f]是TDBISAMTable,则
    开始
    尝试
    //如果我们需要将表升级到dbisam 4
    如果当前版本与此类似。
    我们将DB版本号存储在“系统”表中,并在启动时进行检查。(如果表/字段/值不存在,那么我们知道它是版本0,我们忘记在其中添加该位!)

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

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

    主要的一点是,不要使用基于GUI的工具来修改
    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;