数据库开发和部署策略 (C#)

作者 :Scott Mitchell

首次部署数据驱动应用程序时,可以将开发环境中的数据库盲目复制到生产环境。 但在后续部署中执行盲复制会覆盖输入到生产数据库中的任何数据。 相反,部署数据库涉及将自上次部署以来对开发数据库所做的更改应用到生产数据库。 本教程介绍这些挑战,并提供各种策略来帮助记录和应用自上次部署以来对数据库所做的更改。

简介

如前面的教程中所述,部署 ASP.NET 应用程序需要将相关内容从开发环境复制到生产环境。 部署不是一次性事件,而是每次发布新版软件时或者识别和解决 bug 或安全问题时都会发生的情况。 将 ASP.NET 页、图像、JavaScript 文件和其他此类文件复制到生产环境时,无需担心自上次部署以来这些文件的更改情况。 你可以盲目地将文件复制到生产环境,从而覆盖现有内容。 遗憾的是,这种简单性不能扩展到部署数据库。

首次部署数据驱动应用程序时,可以将开发环境中的数据库盲目复制到生产环境。 但在后续部署中执行盲复制会覆盖输入到生产数据库中的任何数据。 相反,部署数据库涉及将自上次部署以来对开发 数据库所做的更改应用到 生产数据库。 本教程介绍这些挑战,并提供各种策略来帮助记录和应用自上次部署以来对数据库所做的更改。

部署数据库的挑战

在首次部署数据驱动应用程序之前,只有一个数据库,即开发环境中的数据库,这就是为什么首次部署数据驱动应用程序时,可以盲目地将开发环境中的数据库复制到生产环境。 但是,部署应用程序后,数据库有两个副本:一个在开发中,一个在生产环境中。

在部署之间,开发和生产数据库可能会变得不同步。虽然生产数据库的架构保持不变,但随着新功能的添加,开发数据库的架构可能会更改。 可以添加或删除列、表、视图或存储过程。 可能还会有重要数据添加到开发数据库。 许多数据驱动的应用程序包括使用用户不可编辑的硬编码、特定于应用程序的数据填充的查找表。 例如,拍卖网站可能有一个下拉列表,其中包含描述要拍卖的商品条件的选项:“新建”、“新”、“良好”和“公平”。 通常最好将它们放在数据库表中,而不是直接在下拉列表中对这些选项进行硬编码。 如果在开发过程中将名为 Poor 的新条件添加到表中,则在部署应用程序时,需要将相同的记录添加到生产数据库中的查找表中。

理想情况下,部署数据库涉及将数据库从开发复制到生产环境。 但请记住,在部署应用程序并恢复开发后,生产数据库将填充来自真实用户的真实数据。 因此,如果在下一个部署中简单地将数据库从开发复制到生产环境,则会覆盖生产数据库并丢失其现有数据。 最终结果是,部署数据库归结为应用自上次部署以来对开发数据库所做的更改。

由于部署数据库涉及应用架构中的更改,并且可能涉及自上次部署以来的数据,因此必须在部署时) (或确定更改历史记录,以便可以在生产环境中应用这些更改。 有多种技术可用于管理和将更改应用于数据模型。

定义基线

若要维护对应用程序数据库的更改,需要有一些启动状态,即应用更改的基线。 在一个极端的情况下,起始状态可能是没有表、视图或存储过程的空数据库。 此类基线会导致较大的更改日志,因为它必须包括创建所有数据库表、视图和存储过程以及初始部署后所做的任何更改。 在范围的另一端,可以将基线设置为最初部署到生产环境的数据库版本。 此选项会导致更改日志小得多,因为它只包括第一次部署后对数据库所做的更改。 这就是我喜欢的方法。 当然,可以选择一种更中间的方法,将基线定义为数据库初始创建与首次部署数据库之间的某个点。

选择基线后,请考虑生成可执行以重新创建基线版本的 SQL 脚本。 借助此类脚本,可以快速重新创建数据库的基线版本。 此功能在大型项目中特别有用,其中可能有多个开发人员在项目或其他环境(例如测试或暂存)上工作,而每个开发人员都需要自己的数据库副本。

有多种工具可用于生成基线版本的 SQL 脚本。 SQL Server Management Studio (SSMS) 可以右键单击数据库,转到“任务”子菜单,然后选择“生成脚本”选项。 这会启动脚本向导,可以指示该向导生成一个文件,其中包含用于创建数据库 对象的 SQL 命令。 另一个选项是数据库发布向导,它可以生成 SQL 命令,以便不仅创建数据库架构,还可以创建数据库表中的数据。 在部署数据库教程中详细检查 了数据库 发布向导。 无论使用哪种工具,最终都应该有一个脚本文件,如果需要,可以使用该文件重新创建数据库的基线版本。

在散文中记录数据库更改

在开发阶段维护数据模型更改日志的最简单方法是在散文中记录更改。 例如,如果在开发已部署的应用程序期间向Employees表添加新列,从Orders表中删除列,并 () ProductCategories 添加新表,则会维护具有以下历史记录的文本文件或 Microsoft Word 文档:

更改日期 更改详细信息
2009-02-03: 向表添加了列 DepartmentID (int、NOT NULL) Employees 。 从 向 添加了外键约束Departments.DepartmentIDEmployees.DepartmentID
2009-02-05: 从表中删除了OrdersTotalWeight。 关联 OrderDetails 记录中已捕获的数据。
2009-02-12: 已创建 ProductCategories 表。 有三列: ProductCategoryID (intNOT NULLIDENTITY) 、 CategoryName (nvarchar(50)NOT NULL) 和 Active (bitNOT NULL) 。 向 添加了主键约束 ProductCategoryID,向 添加了默认值 1 Active

此方法有许多缺点。 对于初学者来说,自动化是没有希望的。 每当需要将这些更改应用到数据库(例如部署应用程序时),开发人员都必须手动实现每个更改,一次一个。 此外,如果需要使用更改日志从基线重新构造数据库的特定版本,则随着日志大小的增长,这样做需要花费越来越多的时间。 此方法的另一个缺点是,每个更改日志条目的清晰度和详细程度留给记录更改的人员。 在具有多个开发人员的团队中,有些人可能会比其他开发人员更详细、更易读或更精确的条目。 此外,还可能存在拼写错误和其他与人为相关的数据输入错误。

在散文中记录数据库更改的主要优点是简单。 无需熟悉用于创建和更改数据库对象的 SQL 语法。 相反,你可以记录散文中的更改,并通过SQL Server Management Studio图形用户界面实现这些更改。

不可否认,在散文中维护更改日志并不十分复杂,并且不适用于某些项目,例如范围较大、数据模型频繁更改的项目或涉及多个开发人员的项目。 但我已经看到此方法在小型单人项目中非常出色,这些项目仅偶尔对数据模型进行更改,并且独奏开发人员在创建和更改数据库对象的 SQL 语法方面没有强大的背景。

注意

虽然从技术上讲,更改日志中的信息仅在部署时之前才需要,但我建议保留更改历史记录。 但是,考虑为每个数据库版本使用不同的更改日志文件,而不是维护一个不断增长的更改日志文件。 通常,每次部署数据库时,你都会希望对数据库进行版本控制。 通过维护更改日志日志,可以从基线开始,通过执行从版本 1 开始的更改日志脚本并一直执行,直到达到需要重新创建的版本,从而重新创建任何数据库版本。

记录 SQL 更改语句

在 prose 中维护更改日志的主要缺点是缺乏自动化。 理想情况下,在部署时对生产数据库实施数据库更改就像单击按钮来执行脚本一样简单,而不必手动执行指令列表。 通过维护包含用于更改数据模型的 SQL 命令的更改日志,可以实现这种自动化。

SQL 语法包含许多用于创建和修改各种数据库对象的语句。 例如, CREATE TABLE 语句在执行时,会创建具有指定列和约束的新表。 ALTER TABLE 语句修改现有表,添加、删除或修改其列或约束。 还有用于创建、修改和删除索引、视图、用户定义的函数、存储过程、触发器和其他数据库对象的语句。

回到我们前面的示例,假设在开发已部署的应用程序期间,向表添加新列 Employees ,从 Orders 表中删除列, (ProductCategories) 添加新表。 此类操作将导致包含以下 SQL 命令的更改日志文件:

-- Add the DepartmentID column 

ALTER TABLE [Employees] ADD [DepartmentID] 
int NOT NULL 

-- Add a foreign key constraint between Departments.DepartmentID and Employees.DepartmentID
ALTER TABLE [Employees] ADD 
CONSTRAINT [FK_Departments_DepartmentID]
      FOREIGN 
KEY ([DepartmentID]) 
      REFERENCES 
[Departments] ([DepartmentID]) 

-- Remove TotalWeight column from Orders
ALTER TABLE [Orders] DROP COLUMN 
[TotalWeight] 

-- Create the ProductCategories table

CREATE TABLE [ProductCategories]
(
      [ProductCategoryID] 
int IDENTITY(1,1) NOT NULL,
      [CategoryName] 
nvarchar(50) NOT NULL,
      [Active] 
bit NOT NULL CONSTRAINT [DF_ProductCategories_Active]  DEFAULT 
((1)),
      CONSTRAINT 
[PK_ProductCategories] PRIMARY KEY CLUSTERED ( [ProductCategoryID])
)

在部署时将这些更改推送到生产数据库是一键式操作:打开SQL Server Management Studio,连接到生产数据库,打开“新建查询”窗口,粘贴更改日志的内容,然后单击“执行”运行脚本。

使用比较工具同步数据模型

在散文中记录数据库更改很容易,但实现这些更改需要开发人员每次对生产数据库进行一次更改:记录更改 SQL 命令使在生产数据库上实现这些更改就像单击按钮一样简单快捷,但需要学习和掌握创建和更改数据库对象的 SQL 语句和语法。 数据库比较工具充分利用这两种方法,并丢弃最差的方法。

数据库比较工具会比较两个数据库的架构或数据,并显示一个摘要报告,其中显示了数据库的不同之处。 然后,单击按钮即可生成用于同步一个或多个数据库对象的 SQL 命令。 简言之,可以使用数据库比较工具在部署时比较开发和生产数据库,生成包含 SQL 命令的文件,执行这些命令时,会将更改应用于生产数据库的架构,以便它镜像开发数据库的架构。

许多不同的供应商提供了各种第三方数据库比较工具。 其中一个示例是 RED Gate SoftwareSQL Compare。 让我们演练使用 SQL Compare 来比较和同步开发和生产数据库架构的过程。

注意

撰写本文时,SQL Compare 的当前版本为 7.1 版,标准版的价格为 395 美元。 接下来,可以下载 14 天免费试用版。

当 SQL Compare 启动时,将打开“比较项目”对话框,其中显示了保存的 SQL Compare 项目。 创建新项目。 这会启动“项目配置”向导,该向导会提示提供有关数据库的信息进行比较 (请参阅图 1) 。 输入开发和生产环境数据库的信息。

比较开发和生产数据库

图 1:比较开发和生产数据库 (单击以查看全尺寸图像)

注意

如果开发环境数据库是网站文件夹中的 SQL Express Edition 数据库文件App_Data,则需要在SQL Server Express数据库服务器中注册该数据库,以便从图 1 所示的对话框中将其选中。 实现此目的的最简单方法是打开SQL Server Management Studio (SSMS) ,连接到SQL Server Express数据库服务器,然后附加数据库。 如果计算机上未安装 SSMS,可以下载并安装免费SQL Server Management Studio

除了选择要比较的数据库外,还可以从“选项”选项卡中指定各种比较设置。可能需要启用的一个选项是“忽略约束和索引名称”。回想一下,在前面的教程中,我们已将应用程序服务数据库对象添加到开发和生产数据库。 如果使用 aspnet_regsql.exe 该工具在生产数据库上创建这些对象,则会发现开发和生产数据库之间的主键和唯一约束名称不同。 因此,SQL Compare 会将所有应用程序服务表标记为不同。 可以取消选中“忽略约束和索引名称”并同步约束名称,也可以指示 SQL Compare 忽略这些差异。

选择要比较 (的数据库并查看) 比较选项后,单击“立即比较”按钮开始比较。 在接下来的几秒钟内,SQL Compare 会检查两个数据库的架构,并生成一个报告,说明它们有何不同。 我特意对开发数据库进行了一些修改,以显示如何在 SQL Compare 接口中记录此类差异。 如图 2 所示,我向表添加了一个BirthDate列,从Books表中删除了该ISBN列,并添加了一个新表,Ratings该表旨在使访问网站的用户对审阅的Authors书籍进行评分。

注意

本教程中的数据模型更改是为了说明如何使用数据库比较工具。 将来的教程中不会在数据库中找到这些更改。

SQL 比较Lists开发和生产数据库之间的差异

图 2:SQL 比较Lists开发数据库与生产数据库之间的差异 (单击以查看全尺寸图像)

SQL Compare 将数据库对象分解成组,快速显示两个数据库中存在但不同的对象、哪些对象存在于一个数据库中,但另一个数据库中存在哪些对象,以及哪些对象是相同的。 如你所看到的,这两个数据库中存在两个对象,但不同:添加了列的 Authors 表和 Books 删除了一个列的表。 有一个对象仅存在于开发数据库中,即新创建的 Ratings 表。 这两个数据库中有 117 个对象是相同的。

选择数据库对象将显示“SQL 差异”窗口,其中显示了这些对象的差异。 图 2 底部显示的“SQL 差异”窗口突出显示了开发数据库中的 Authors 表具有 BirthDate 列,该列在 Authors 生产数据库的表中找不到。

查看差异并选择要同步的对象后,下一步是生成所需的 SQL 命令,以更新生产数据库的架构以匹配开发数据库。 这是通过同步向导实现的。 同步向导确认要同步的对象,并汇总了操作计划 (请参阅图 3) 。 可以立即同步数据库,或使用可在闲暇时运行的 SQL 命令生成脚本。

使用同步向导同步数据库架构

图 3:使用同步向导同步数据库架构 (单击以查看全尺寸映像)

数据库比较工具(如 Red Gate Software SQL Compare)使对开发数据库架构的更改应用到生产数据库就像单击一样简单。

注意

SQL 比较比较并同步两 个数据库架构。 遗憾的是,它不会比较和同步两个数据库表中的数据。 Red Gate Software 确实提供了一个名为 SQL 数据比较 的产品,用于比较和同步两个数据库之间的数据,但它是 SQL Compare 的单独产品,成本为 395 美元。

在部署期间使应用程序脱机

正如我们在这些教程中看到的,部署是一个涉及多个步骤的过程:将 ASP.NET 页、母版页、CSS 文件、JavaScript 文件、图像和其他必要内容从开发环境复制到生产环境;根据需要复制生产环境特定的配置信息;,并将更改应用到自上次部署以来的数据模型。 根据文件数量和数据库更改的复杂性,这些步骤可能需要几秒钟到几分钟才能完成。 在此时段内,Web 应用程序处于不断变化状态,访问该网站的用户可能会遇到错误或意外行为。

部署网站时,最好将 Web 应用程序“脱机”,直到部署完成。 将应用程序脱机 (,并在部署过程完成后将其备份) 就像上传文件然后删除一样简单。 从 ASP.NET 2.0 开始,在应用程序的根目录中仅存在名为 app_offline.htm 的文件,整个网站将处于“脱机”状态。对该网站 ASP.NET 页面的任何请求都会使用文件的内容 app_offline.htm 自动响应。 删除该文件后,应用程序将重新联机。

那么,在部署期间使应用程序脱机非常简单,只需在开始部署过程之前将文件上传到 app_offline.htm 生产环境的根目录,然后在部署完成后将其删除 (或将其重命名为其他) 。 有关此技术的详细信息,请参阅 John Peterson 的文章,将 ASP.NET 应用程序脱机

总结

部署数据驱动的应用程序main挑战集中在部署数据库方面。 由于数据库有两个版本(一个在开发环境中,另一个在生产环境中),因此随着开发中添加新功能,这两个数据库架构可能会变得不同步。 而且,由于生产数据库填充了来自真实用户的真实数据,因此不能像在) ( (ASP.NET 页、图像文件等时那样,使用修改的开发数据库覆盖生产数据库。 相反,部署数据库需要实现自上次部署以来对生产数据库上的开发数据库所做的一组精确更改。

本教程介绍了用于维护和应用数据库更改日志的三种方法。 最简单的方法是记录散文的变化。 虽然此策略使在生产数据库上实现这些更改是一个手动过程,但它不需要了解创建和更改数据库对象的 SQL 命令。 在具有多个开发人员的大型项目或项目中,一种更复杂的方法是将更改记录为一系列 SQL 命令。 这大大加快了对目标数据库的这些更改的推出。 可以通过使用数据库比较工具(例如 Red Gate Software 的 SQL 比较)实现这两种方法中的最佳方法。

本教程将重点介绍如何部署数据驱动应用程序。 下一组教程介绍如何响应生产环境中的错误。 我们将介绍如何显示友好的错误页面,而不是显示死亡的黄屏。 我们将了解如何记录错误的详细信息,以及如何在发生此类错误时发出警报。

编程快乐!