使用 Delphi 对已安装应用程序进行数据库版本控制

发布于 2024-08-12 04:33:44 字数 893 浏览 1 评论 0原文

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

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

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

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

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

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

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

  2. 区分引用与实际数据库的最佳方法。

  3. 生成DDL来更新数据库的最佳方法。

I'm working on a number of Delphi applications that will need to upgrade their own database structures in the field when new versions are released and when users choose to install additional modules. The applications are using a variety of embedded databases (DBISAM and Jet currently, but this may change).

In the past I've done this with DBISAM using the user version numbers than can be stored with each table. I shipped an extra, empty set of database files and, at start-up, compared the version numbers of each table using the FieldDefs to update the installed table if necessary. While this worked I found it clumsy to have to ship a spare copy of the database and newer versions of DBISAM have changed the table restructuring methodology so that I'll need to rewrite this anyway.

I can see two ways of implementing this: storing a version number with the database and using DDL scripts to get from older versions to newer versions or storing a reference version of the database structure inside the application, comparing the reference to the database on start-up, and having the application generate DDL commands to upgrade the database.

I think that I'll probably have to implement parts of both. I won't want the application to diff the database against the reference structure every time the application starts (too slow), so I'll need a database structure version number to detect whether the user is using an outdated structure. However, I'm not sure I can trust pre-written scripts to do the structural upgrade when the database could have been partially updated in the past or when the user may have themselves changed the database structure, so I'm inclined to use a reference diff for the actual update.

Researching the question I've found a couple of database versioning tools but they all seem targeted towards SQL Server and are implemented outside the actual application. I'm looking for a process that would be tightly integrated into my application and that could be adapted to different database requirements (I know that I'll have to write adapters, custom descendant classes, or event code to handle differences in DDL for various databases, that doesn't bother me).

Does anyone know of anything off the shelf that does this or failing that, does anyone have any thoughts on:

  1. The best way to store a reference version of a generic relational database structure inside an application.

  2. The best way to diff the reference against the actual database.

  3. The best way to generate DDL to update the database.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

请远离我 2024-08-19 04:33:44

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

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

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

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

Similar story here.
We store a DB version number in a 'system' table and check that on startup. (If the table/field/value doesn't exist then we know it's version 0 where we forgot to add that bit in!)

During development as and when we need to upgrade the database we write a DDL script to do the work and once happy that it's working OK it gets added as a text resource to the app.

When the app determines that it needs to upgrade it loads the appropriate resource(s) and runs it/them. If it needs to upgrade several versions it must run each script in order. Turns out to be only a few lines of code in the end.

The main point being that instead of using the GUI based tools to modify tables in an ad-hoc or 'random' manner we actually write the DDL straight away. This makes it far easier, when the time comes, to build the full upgrade script. And structure diff'ing isn't required.

友谊不毕业 2024-08-19 04:33:44

我在这里有一篇关于如何进行 dbisam 数据库版本控制< /a> 和 sql server

重要的部分是:

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

我有一个数据模块,
TdmodCheck数据库。这有一个
每个表的 TdbisamTable 组件
在数据库中。表组件
包含表中的所有字段并且
每当表被更新时
改变了。

要更改数据库,
使用了以下过程:

  1. 增加应用中的版本号
  2. 进行并测试数据库更改。
  3. 更新 TdmodCheckDatabase 中受影响的表
  4. 如果有必要(很少)添加进一步的升级查询
    TdmodCheck数据库。例如设置
    新字段的值,或添加新字段
    数据行。
  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;

I have a blog post here about how I do dbisam database versioning and sql server.

The important parts are:

Because dbisam doesn't support views,
the version number is stored (along
with a bunch of other info) in an ini
file in the database directory.

I have a datamodule,
TdmodCheckDatabase. This has a
TdbisamTable component for every table
in the database. The table component
contains all fields in the table and
is updated whenever the table is
changed.

To make database changes, the
following process was used:

  1. Increase the version number in the application
  2. Make and test DB changes.
  3. Update the affected tables in TdmodCheckDatabase
  4. If necessary (rarely) add further upgrade queries to
    TdmodCheckDatabase. E.g. to set the
    values of new fields, or to add new
    data rows.
  5. Generate a CreateDatabase unit script using the supplied database
    tools.
  6. Update unit tests to suit the new db

When the application is run, it goes
through the following process

  1. If no database is found, then run CreateDatabase unit and then do
    step 3
  2. Get the current version number from the database ini file
  3. If it is less than the expected version number then
    Run CreateDatabase (to create any new tables)
    Check every table component in TdmodCheckDatabase
    Apply any table changes
    run any manual upgrade scripts
  4. Update the version number in the database ini file

A code sample is

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;
半窗疏影 2024-08-19 04:33:44

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

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

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

I'm Using ADO for my databases. I also use a version number scheme, but only as a sanity check. I have a program I developed which uses the Connection.GetTableNames and Connection.GetFieldNames to identify any discrepancy against an XML document which describes the "master" database. If there is a discrepancy, then I build the appropriate SQL to create the missing fields. I never drop additional ones.

I then have a dbpatch table, which contains a list of patches identified by a unique name. If there are specific patches missing, then they are applied and the appropriate record is added to the dbpatch table. Most often this is new stored procs, or field resizing, or indexes

I also maintain a min-db-version, which is also checked since I allow users to use an older version of the client, I only allow them to use a version that is >= min-db-version and <= cur-db-version.

肤浅与狂妄 2024-08-19 04:33:44

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

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);

What I do is store a version number in the database, and a version number in the application. Every time I need to change the database structure, I create some code update the structure of the database, and increase the version number in the application. When the application starts, it compares, numbers, and if need be runs some code to update the database structure AND update the database's version number. Thus the database is now up to date with the application. My code is something like

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 just runs the necessary code something like :

procedure UpdateStructure(const aVersion : Integer);
begin
  case aVersion of
    1 : //some db code
    2 : //some more db code
    ...
    ...
  end;
  UpdateDatabaseVersion(aVersion);
end;  

You can actually use the same code to create the database from scratch

CreateDatabase;
for i := 1 to AppVersion do
  UpdateStructure(i);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文