65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 nHydrate 跟踪数据库更改

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (5投票s)

2011年2月21日

Ms-PL

9分钟阅读

viewsIcon

20100

使用模型驱动开发使您的 API 和数据库保持同步。

引言

在开发过程中,您总是会遇到数据库版本控制的问题。更具体地说,实际上并没有真正意义上的版本控制,因此您必须担心使数据库与 API 代码的最新版本保持同步。人们会创建更新脚本,并使用 SQL Compare 或其他比较工具。最终您会得到一个庞大的脚本,只需运行一次;或者许多较小的脚本,需要知道何时运行。不可避免地,有些脚本应该只运行一次,例如数据插入或字段删除。换句话说,这很混乱。许多人有许多解决方案,但大多数都归结为开发一个流程。例如,开发团队会决定为每个产品版本创建一个单独的文件夹,以便在正确的时间部署脚本。或者可能采用文件命名方案,如“v1.2.3.4.sql”或“2011-06-03.sql”,其中版本号、日期或其他标识符决定了脚本的运行方式。最终,这只是一个巨大的手动过程,如果有人搞砸了这个过程,就会造成麻烦。嗯,有一个更简单的方法。

一种新方法

使用 nHydrate 数据库安装程序是在数据库发生更改时跟踪更改和对数据库进行版本控制的好方法。数据库本身并没有真正的版本或历史记录,但您可以模拟它们。nHydrate 通过利用 SQL Server 的元数据属性来实现这一点。SQL 有一个很少使用的功能,允许您将小的名称/值字符串对关联到字段、表或整个数据库。数据库安装程序使用这些值来保存版本信息。与模型结合使用时,您可以创建一个与数据库版本同步的 API。

要使用此方法创建一个示例,您需要 nHydrate,可以从 nHydrate.CodePlex.com 下载。安装后,您需要将 ADO.NET 生成器包添加到您的生成器库中。此包包含创建 ADO.NET 数据访问层和跟踪数据库安装程序项目所需的所有内容。从那时起,所有更改都会被跟踪。严格来说,您的模型更改不是实时跟踪的,只是看起来是这样。每次生成数据库安装程序项目时,都会保存当前模型的一个快照。下次生成时,框架会查找最后一个备份文件,并比较两个模型以查找添加、删除或更改的字段和实体等差异。一个不同的脚本会添加到数据库安装程序项目中,并命名为特定的版本。您可以根据需要多次重新生成。每个差异文件都按照正确的顺序添加,并带有特定的版本号。

现在,在某个时候,您将使用数据库安装程序创建或更新数据库。执行此操作时,框架知道要运行哪些脚本,同样重要的是,知道要不运行哪些脚本。如果创建数据库,此过程很简单,因为所有脚本都会被运行。但是,在运行更新时,安装程序会检查数据库中的元数据版本信息,然后确定要针对数据库运行哪些脚本。只有版本号更高的脚本才会针对数据库服务器运行。在创建/更新操作完成后,数据库中的元数据版本号会被更新,从而对数据库进行版本控制。下次运行安装程序时,这将是运行所有新生成的差异脚本的起点。此功能还有另一个好处:您可以在同一代码库上维护多个数据库,并在不同时间更新它们。

我使用此功能为网站开发了一个代码库。这个代码库实际上运行了四个不同的网站。我们有一个自定义资源程序集,用于样式表、自定义页面、配置等。因此,相同的网站代码和相同的数据库结构支持所有站点。现在,我实际上有四个不同的网站,每个网站都连接到自己的数据库。有时,我对一个网站进行了更改,例如添加了一个新功能,这个功能需要推送到一个站点,但不一定立即推送到其他站点。我可以部署我的网站代码并运行数据库安装程序来更新数据库到最新版本。现在我拥有多个数据库,它们位于不同版本的 API 上。我可以完全独立地将任何数据库从任何先前版本升级到最新版本。

这在开发周期中非常有用。您可能每月甚至更长时间(例如一年)才将一个应用程序部署到生产环境一次;然而,在此期间,您进行了大量的微小更改。数据库跟踪会捕获所有这些小更改,并允许您在准备部署(或测试)应用程序时将它们作为单个补丁应用到数据库。

现在您已经了解了进行此操作的背景和逻辑,示例将非常简单。我将创建一个带有 Customer 实体的模型。然后我将生成。为了模拟我在模型上工作一段时间,我将进行一次更改并重新生成,并重复这个模式几次。每次这样做时,都会创建一个新的更新脚本。

我首先打开 Visual Studio .NET 并创建一个空白解决方案。然后,我在解决方案资源管理器中右键单击解决方案,选择“添加 | 新项”菜单。在出现的对话框中,选择模型部分(仅在安装了 nHydrate 时才存在),然后选择模型项。这将创建一个新的 nHydrate 模型,并会弹出一个向导。设置公司和项目属性,然后按“确定”按钮。我分别将它们命名为“Acme”和“DatabaseDemo”。现在我有一个空白模型。我右键单击“表”节点,并使用菜单创建表。然后,我右键单击其“列”节点并添加列。该图显示了一个起始模型,其中有一个带有主键和名字字段的 Customer 表。

生成后,我的 VS.NET 解决方案中有两个新项目。第一个是 ADO.NET API,第二个是数据库安装程序项目。

现在我执行以下任务。我向 Customer 表中添加了另一个名为“LastName”的列,并重新生成。然后我添加了另一个名为“JunkColumn”的列,并重新生成。现在,当我查看安装程序项目下的“Generated”文件夹时,我看到了三个文件。请注意,脚本以版本号命名。架构是主版本、次版本、修订版本、构建版本和生成版本。前四个数字在模型用户界面中控制。您可以随时更改它们。最后一个生成版本由框架控制。您无法设置此值,因为它在每次生成后都会递增。

第一个以零开头的文件什么也没有,因为它来自初始生成。接下来的两个文件包含我们每次生成之间的更改。“0_0_0_0_1_GeneratedScript.sql”文件包含用于 LastName 的添加字段脚本。

if not exists (select * from syscolumns c inner join sysobjects o on 
   c.id = o.id where c.name = 'LastName' and o.name = 'Customer')
ALTER TABLE [dbo].[Customer] ADD [LastName] [VarChar] (50) NOT NULL 
GO

--The file "0_0_0_0_2_GeneratedScript.sql" has 
--the change for the add field script for JunkField.

if not exists (select * from syscolumns c inner join sysobjects o on 
   c.id = o.id where c.name = 'JunkColumn' and o.name = 'Customer')
ALTER TABLE [dbo].[Customer] ADD [JunkColumn] [Int] NULL 
GO

现在我们可以运行安装程序并创建一个匹配此架构的数据库。您可以直接在 VS.NET 环境中执行此操作。打开安装程序项目的属性窗口,然后导航到“调试”选项卡。在“启动外部程序”框中,插入 .NET Framework 提供的 InstallUtil.exe 应用程序的路径。我的路径如下:“C:\Windows\Microsoft.NET\Framework\v2.0.50727\InstallUtil.exe”。在命令行参数框中,输入项目程序集的名称。根据我们上面使用的设置,我的名称是“Acme.DatabaseDemo.Install.dll”。现在,您可以将此项目设置为启动项目并按 F5 来运行它,或者在解决方案资源管理器中右键单击项目并选择“调试 | 启动新实例”菜单。

运行此项目时会显示一个对话框屏幕。它允许您创建或更新数据库。我选择了创建数据库选项卡,并输入了我希望使用的设置。按“确定”按钮后,会创建一个具有正确架构和所有存储过程的数据库。

现在,假设这是我们的生产数据库,我们希望对其进行一些更改。我打开我的模型,并从 Customer 表中删除 JunkField。重新生成后,在我的安装程序项目中又创建了一个文件,这次名为“0_0_0_0_3_GeneratedScript.sql”。现在我可以看到其中有一个删除脚本。下次运行数据库安装程序时,此字段将从我的数据库中删除。

select 'ALTER TABLE [dbo].[Customer] DROP CONSTRAINT ' + 
       [name] as 'sql' into #t from sysobjects where id IN( 
       select sc.cdefault FROM dbo.sysobjects SO INNER JOIN 
       dbo.syscolumns SC ON SO.id = SC.id LEFT JOIN 
       dbo.syscomments SM ON SC.cdefault = SM.id WHERE SO.xtype = 'U' 
and SO.NAME = 'Customer' and SC.NAME = 'JunkColumn' )
declare @sql [nvarchar] (1000)
SELECT @sql = MAX([sql]) from #t
exec (@sql)
drop table #t
if exists (select * from dbo.sysindexes where name = 'IX_CUSTOMER_JUNKCOLUMN')
ALTER TABLE [Customer] DROP CONSTRAINT [IX_CUSTOMER_JUNKCOLUMN]
if exists (select * from dbo.sysindexes where name = 'IDX_CUSTOMER_JUNKCOLUMN')
DROP INDEX [IDX_CUSTOMER_JUNKCOLUMN] ON [Customer]

if exists (select * from syscolumns c inner join sysobjects o on 
   c.id = o.id where c.name = 'JunkColumn' and o.name = 'Customer')
ALTER TABLE [dbo].[Customer] DROP COLUMN [JunkColumn]
GO

我现在将再次运行安装程序,但这次我选择更新选项卡,因为数据库已存在。

请记住,在某些情况下,您需要手动编辑升级脚本。例如,如果您要向表中添加一个非可为空且没有默认值的字段,或者执行一些自定义数据插入,您需要在正确的位置修改或添加到脚本中。这与任何自定义编码没有什么不同。如果流程或情况未在模型中定义,则无法从模型中生成。运行此最新构建后,JunkField 将从数据库中删除。

现在,在对话框中,您会注意到一个“查看历史记录”按钮。这将显示先前的数据库版本及其关联的日期。这是一个完整的历史记录,使您能够查看所有数据库更新的实例。

摘要

正如您所见,跟踪数据库更改不仅无缝,而且比使用第三方商业工具并跟踪需要何时应用的更改要容易得多。此外,其他工具没有数据库版本控制的概念。数据库安装程序模板允许您随时更改模型,并确信您的更改最终会合并到您的生产数据库中。此程序集也可以从命令行运行,因此您可以将其添加到部署脚本或批处理文件中。

另一个细微之处是,通过使用模型驱动开发(领域驱动设计,DDD),数据库不是您的模型。SQL Compare 和其他比较工具依赖于您有一个黄金副本数据库的事实。当然,您必须跟踪您黄金副本的所有更改,以确保您没有丢失任何更改。使用 DDD,所有更改都是针对模型执行的,而不是针对数据库。数据库本身由模型管理。由于您的所有设计都基于模型,因此您可以从此设计中创建任何项目或项目集。在此示例中,我们基于相同的模型使用了两个完全不同的生成器模板。这使得扩展您的应用程序框架更加容易。

© . All rights reserved.