如何在我的 ssdt post 部署脚本中有条件地包含大型脚本?

How can I conditionally include large scripts in my ssdt post deployment script?

在我们的 SSDT 项目中,我们有一个庞大的脚本,其中包含大量用于从旧系统导入数据的 INSERT 语句。使用 sqlcmd 变量,我希望能够有条件地将文件包含到 post 部署脚本中。

我们目前正在使用 :r 语法,其中包括内联脚本:

IF '$(ImportData)' = 'true'
BEGIN
  :r .\Import\OldSystem.sql
END

这是一个问题,因为无论 $(ImportData) 是真还是假,脚本都被包含在 内联 中,并且文件太大以至于减慢了构建速度大约 15 分钟。

是否有另一种方法可以有条件地包含此脚本文件,以免降低构建速度?

始终包含使用 :r 引用的脚本。您有几个选择,但我会首先验证如果您删除脚本,它会将性能提高到您希望达到的水平。

最简单的方法是将它保留在整个构建过程之外并更改您的部署过程,使其成为两步操作(部署 DAC,然后部署脚本)。这样做的好处是您可以在 ssdt 进程之外做一些事情,但不利的是您不会得到诸如自动禁用对部署中更改的表的约束之类的东西。

第二种方法是在构建时不将脚本包含在部署中,而是创建一个 AfterBuild msbuild 任务,将脚本作为 post 部署脚本添加到 dacpac 中。 dacpac 是一个 zip 文件,因此您可以使用 .net 打包 Api 添加一个名为 postdeploy.sql 的部分,然后将其包含在部署过程中。

这两种方式都意味着您失去了验证,因此您可能希望将其保存在一个单独的 ssdt 项目中,该项目具有对您的主项目的 "same database" 引用,它会在更改时减慢构建速度,但应该剩下的时间要快点。

这是我必须这样做的方式。

1) 创建一个虚拟 post-部署脚本。

2) 在您的项目中为每个部署方案创建构建配置。

3) 使用预构建事件来确定要使用的 post 部署配置。 您可以为每个配置创建单独的脚本,也可以在预构建事件中动态构建 post-deploy 脚本。无论哪种方式,您所做的事情都基于始终存在于构建事件中的 $(configuration) 的值。

如果您使用单独的静态脚本,您的构建事件只需要复制适当的静态文件,用在该部署场景中有用的任何脚本覆盖虚拟 post-deploy。

在我的例子中,我不得不使用动态生成,因为决定要包含哪些脚本需要知道要部署到的数据库的当前状态。因此,我使用配置变量来告诉我要部署到哪个环境,然后使用 SQLCMD 脚本并将 :OUT 设置为我的 Post-Deploy 脚本位置。因此,我的预构建脚本将动态编写 post-deploy 脚本。

无论哪种方式,一旦构建完成并且正常的部署过程开始,Post-Deploy 脚本就包含了我想要的 :r 命令。

这是我在预构建中调用的 SQLCMD 脚本的示例。

:OUT .\Script.DynamicPostDeployment.sql

PRINT ' /*';
PRINT '     DO NOT MANUALLY MODIFY THIS SCRIPT.                                               ';
PRINT '                                                                                       ';
PRINT '     It is overwritten during build.                                                   ';
PRINT '     Content IS based on the Configuration variable (Debug, Dev, Sit, UAT, Release...) ';
PRINT '                                                                                       ';
PRINT '     Modify Script.PostDeployment.sql to effect changes in executable content.         ';
PRINT ' */';
PRINT 'PRINT ''PostDeployment script starting at''+CAST(GETDATE() AS nvarchar)+'' with Configuration = $(Configuration)'';';
PRINT 'GO';
IF '$(Configuration)' IN ('Debug','Dev','Sit')
BEGIN
    IF (SELECT IsNeeded FROM rESxStage.StageRebuildNeeded)=1
    BEGIN
        -- These get a GO statement after every file because most are really HUGE
        PRINT 'PRINT ''ETL data was needed and started at''+CAST(GETDATE() AS nvarchar);';
        PRINT '                                                  ';
        PRINT 'EXEC iESxETL.DeleteAllSchemaData ''pExternalETL'';';
        PRINT 'GO';
        PRINT ':r .\PopulateExternalData.sql         ';
....

而不是用另一个混淆我之前的答案。有一个非常简单的选项的特殊情况。

为每种执行可能性创建单独的 SQLCMD 输入文件。 这里的关键是使用控制变量的值来命名执行输入文件。

因此,例如,您的发布脚本定义了变量 'Config',它可能具有以下值之一:'Dev'、'QA' 或 'Prod'.

创建 3 个 post 部署脚本,名称为 'DevPostDeploy.sql'、'QAPostDeploy.sql' 和 'ProdPostDeploy.sql'。

像这样编码您的实际 post 部署文件:

:r ."\"$(配置)PostDeploy.sql

这非常类似于构建事件机制,您可以在其中用适当的脚本覆盖脚本,除非您不需要构建事件。但是你依赖于非常具体地命名你的脚本。

我最终混合使用了我们的构建工具 (Jenkins) 和 SSDT 来完成此任务。这就是我所做的:

  1. 为每个写入文本文件的 environment-specific Jenkins 作业添加了构建步骤。我要么编写一个包含导入文件的 SQLCMD 命令,要么根据用户选择的构建参数将其留空。
  2. 通过 :r 在 Post 部署脚本中包含新的文本文件。

就是这样!我也使用相同的方法根据应用程序版本选择要包含在项目中的预部署和 post 部署脚本,除了我从代码中获取版本号并使用 [=23 将其写入文件=] 事件在 VS 而不是在构建工具中。 (我还将文本文件名添加到 .gitignore 所以它不会被提交)