配置源代码管理和管理引用数据
没有源代码管理的 SQL 数据库项目只是某人计算机上的文件文件夹。 一旦该人员的笔记本电脑出现故障,或者两名开发人员编辑同一个存储过程,你就会回到原点。 将项目放入 Git 可为你提供更改历史记录、通过分支和拉取请求实现协作,以及每个 CI/CD 管道所需的基础。
但是,仅源代码管理并不能解决所有问题。 数据库可能具有与架构并存的查阅表、状态代码和默认配置。 还需要针对该数据制定策略。
为 SQL 数据库项目设置源代码管理
由于 SQL 数据库项目只是 .sql 文件和 .sqlproj 文件,因此它自然适合 Git。 每个数据库对象都位于自己的文件中,因此 Git 跟踪对象级别的更改。 修改 Customers 表并更新存储过程的提交,会在差异中准确显示这两个文件,而非包含几十个不相关更改的庞大迁移脚本。
若要开始,请初始化项目文件夹中的 Git 存储库:
cd MyDatabaseProject
git init
git add .
git commit -m "Initial commit of database project"
组织项目文件夹
一致的文件夹结构使项目能够一目了然。 最常见的模式按对象类型对文件进行分组:
MyDatabaseProject/
├── MyDatabaseProject.sqlproj
├── Tables/
│ ├── Customers.sql
│ └── Orders.sql
├── Views/
│ └── vw_ActiveCustomers.sql
├── StoredProcedures/
│ └── usp_GetCustomerOrders.sql
├── Scripts/
│ ├── PostDeployment/
│ │ └── seed-data.sql
│ └── PreDeployment/
│ └── prep-db.sql
└── PostDeploy.sql
对于 SDK 样式的项目,默认的通配模式会自动获取文件夹中的每个 .sql 文件。 不需要手动文件输入。
添加 .gitignore 文件
通过排除生成输出和特定于用户的设置,使存储库专注于源文件:
bin/
obj/
*.dacpac
*.user
在每个 CI/CD 构建过程中,.dacpac 会从项目中重新创建,因此没有必要在 Git 中对其进行跟踪。
使用预部署和部署后脚本管理引用数据
声明性模型处理架构对象(如表、视图和存储过程),但某些数据与架构本身一样重要。 状态代码、查找表、默认配置、区域列表。 如果该数据消失,应用程序会中断。 它属于项目,与依赖它的对象一起进行版本管理。
部署前和部署后脚本可解决此问题。 它们是在部署期间执行的 SQL 脚本,但位于编译的数据库模型之外:
- 部署前脚本在部署计划之前运行。 将其用于在架构更改之前必须完成的任务,例如删除约束或迁移数据。
- 部署后脚本在部署计划完成后运行。 使用它来填充引用数据、种子查找表或设置应用程序默认值。
将脚本添加到项目
项目仅支持一个预部署脚本和一个部署后脚本。 在.sqlproj文件中用PreDeploy和PostDeploy项条目声明它们。
<ItemGroup>
<PreDeploy Include="prep-db.sql" />
</ItemGroup>
<ItemGroup>
<PostDeploy Include="PostDeploy.sql" />
</ItemGroup>
对多个数据文件使用 SQLCMD 包含
一个脚本文件并不意味着一个巨大的文件。 使用 SQLCMD :r 语法从单个入口点拉取多个文件。 典型的 PostDeploy.sql 如下所示:
:r .\Scripts\PostDeployment\seed-statuses.sql
:r .\Scripts\PostDeployment\seed-regions.sql
:r .\Scripts\PostDeployment\seed-app-settings.sql
需要从生成中排除每个引用的文件,否则生成进程会尝试将其编译为架构对象并失败。 在 .sqlproj 文件中,用于 Build Remove 防止编译,并使 None Include 文件在项目中可见:
<ItemGroup>
<Build Remove="Scripts\PostDeployment\seed-statuses.sql" />
<None Include="Scripts\PostDeployment\seed-statuses.sql" />
</ItemGroup>
📝 针对预部署或部署后脚本中的每个引用文件重复此模式。
编写幂等引用数据脚本
部署后脚本在每次部署时运行,而不仅仅是第一次部署。 如果使用纯 INSERT 语句,第二次部署会因重复键冲突而失败。 改用 MERGE 语句使脚本可以安全地重复运行:
MERGE INTO [dbo].[OrderStatuses] AS target
USING (VALUES
(1, N'Pending'),
(2, N'Processing'),
(3, N'Shipped'),
(4, N'Delivered'),
(5, N'Cancelled')
) AS source ([StatusID], [StatusName])
ON target.[StatusID] = source.[StatusID]
WHEN MATCHED THEN
UPDATE SET [StatusName] = source.[StatusName]
WHEN NOT MATCHED THEN
INSERT ([StatusID], [StatusName])
VALUES (source.[StatusID], source.[StatusName]);
小窍门
项目构建后,你可以通过将 .dacpac 文件扩展名更改为 .zip 并提取它,来验证部署前和部署后脚本。 每个脚本类型的单个 .sql 文件包含所有引用文件中合并的 T-SQL 内容。
关键结论
在解决方案级别初始化 Git 存储库,并使用 .gitignore 文件排除生成输出,例如 bin/、obj/ 和 .dacpac 文件。 将架构对象组织到镜像数据库结构的文件夹,每个对象有一个文件。 在部署后脚本中放置引用数据,并使用 SQLCMD :r 语句来确保每个脚本专注于单个表。 若要防止将数据脚本编译为架构对象,请在 .sqlproj 文件中使用 Build Remove 和 None Include。 编写 MERGE 语句而非纯 INSERT 语句,使引用数据脚本具有幂等性,可在每次部署时安全运行。 接下来,你可以通过分支和拉取请求工作流利用此存储库。