DACPAC 架构比较在发布期间的预部署脚本之前运行

DACPAC schema compare runs before pre-deployment scripts during publish

使用 sqlpackage.exe 发布 dacpac 时,它首先运行架构比较,然后运行预部署脚本。这会导致问题,例如,您需要删除 table 或重命名列。模式比较在对象被修改之前完成,部署失败。必须重复发布以考虑新架构。

有人对此有不涉及发布两次的解决方法吗?

从使用 visual studio 转向使用驱动 sqlpackage.exe 的脚本,您可以在比较之前灵活地使用 运行 脚本:

https://the.agilesql.club/Blog/Ed-Elliott/Pre-Deploy-Scripts-In-SSDT-When-Are-They-Run

编辑

Gert Drapers 将其称为 预部署 脚本 here

其实这是一个挑战。如果您需要将不可为空的外键列添加到充满数据的 table - 您只能使用单独的脚本。

如果您是唯一的开发人员 - 这不是问题,但是当您有一个大型团队时,"separate script" 必须在每个 DB 发布之前以某种方式执行。

我们使用的解决方法:

  • 创建单独的 SQL "Before-publish" 脚本(在数据库项目中),其中有一个 属性 [Build action = None]
  • 创建 custom MSBuild Task 调用 SQLCMD.EXE 实用程序传递 "Before-publish" 脚本作为参数,然后调用 SQLPACKAGE.EXE 实用程序传递 DB.dacpac
  • 将自定义 MSBuild 任务的调用添加到 db.sqlproj 文件。例如:
<UsingTask 
        TaskName="MSBuild.MsSql.DeployTask" 
        AssemblyFile="$(MSBuildProjectDirectory)\Deploy\MsBuild.MsSql.DeployTask.dll" />

<Target Name="AfterBuild">
    <DeployTask 
        Configuration="$(Configuration)" 
        DeployConfigPath="$(MSBuildProjectDirectory)\Deploy\Deploy.config" 
        ProjectDirectory="$(MSBuildProjectDirectory)" 
        OutputDirectory="$(OutputPath)" 
        DacVersion="$(DacVersion)">
    </DeployTask>
</Target>

MsBuild.MsSql.DeployTask.dll 以上是自定义 MSBuild 任务。

因此可以从 Visual Studio 调用 "Before-publish" 脚本。

对于 CI,我们使用了批处理文件 (*.bat),其中调用了相同的两个实用程序 (SQLCMD.EXE & SQLPACKAGE.EXE)。

我们得到的最终过程有点复杂,应该在单独的文章中描述 - 这里我只提到一个方向:)

在部署数据库项目期间,我们需要将数据从一个 table 转换为另一个。当然,使用数据库项目是一个问题,因为在预部署中目标 table(列)仍然不存在,但在 post-部署脚本中源 table(专栏)已不存在。

为了将数据从 TableA 转换到 TableB,我们使用了以下想法(此方法可用于任何数据修改):

  1. 开发人员将目标 table (dbo.TableB) 添加到数据库项目并将其部署到本地数据库(无需提交到 SVN)
  2. 他或她创建了预部署转换脚本。诀窍是脚本将结果数据放入临时 table: #TableB
  3. 开发人员删除了数据库项目中的dbo.TableA。假定 table 将在主生成脚本执行期间被删除。
  4. 开发人员编写了一个 post 部署脚本,将数据从 #TableB 复制到刚刚由主脚本创建的 dbo.TableB。
  5. 所有更改都已提交到 SVN 中。

这样我们就不需要预先部署脚本,因为我们将中间数据存储在临时文件中 table。

我想说的是,使用预部署脚本的方法具有相同的中间(临时)数据,但它不是存储在临时 table 中,而是存储在真实 table秒。它发生在部署前和部署前之间。执行预部署脚本后,此中间数据​​消失。

更重要的是,使用临时 tables 的方法允许我们面对以下复杂但真实的情况:假设我们在我们的数据库项目中有两个转换:

  1. 表 A -> 表 B
  2. 表 B -> 表 C

除此之外,我们还有两个数据库:

  1. 具有表A的数据库A
  2. DatabaeB,其中 TableA 已转换为 TableB。数据库 B 中没有表 A。

不过我们可以处理这种情况。我们只需要在部署前执行一项新操作。在转换之前,我们尝试将数据从 dbo.TableA 复制到 #TableA 中。转换脚本仅适用于临时 tables。

让我向您展示这个想法在 DatabaseA 和 DatabaseB 中的工作原理。 假设 DB 项目有两对 pre 和 post 部署脚本:"TableA -> TableB" 和 "TableB -> TableC"。

下面是 "TableB -> TableC" 转换的脚本示例。

预部署脚本

----[The data preparation block]---
--We must prepare to possible transformation
--The condition should verufy the existance of necessary columns
IF  OBJECT_ID('dbo.TableB') IS NOT NULL AND
    OBJECT_ID('tempdb..#TableB') IS NULL
BEGIN
    CREATE TABLE #TableB
    (
        [Id] INT NOT NULL PRIMARY KEY, 
        [Value1] VARCHAR(50) NULL, 
        [Value2] VARCHAR(50) NULL
    )

    INSERT INTO [#TableB]
    SELECT [Id], [Value1], [Value2]
    FROM dbo.TableB
END

----[The data transformation block]---
--The condition of the transformation start
--It is very important. It must be as strict as posible to ward off wrong executions.
--The condition should verufy the existance of necessary columns
--Note that the condition and the transformation must use the #TableA instead of dbo.TableA
IF  OBJECT_ID('tempdb..#TableB') IS NOT NULL 
BEGIN

    CREATE TABLE [#TableC]
    (
        [Id] INT NOT NULL PRIMARY KEY, 
        [Value] VARCHAR(50) NULL
    )

    --Data transformation. The source and destimation tables must be temporary tables.
    INSERT INTO [#TableC]
    SELECT [Id], Value1 + ' '+ Value2 as Value
    FROM [#TableB]

END

Post-部署脚本

--Here must be a strict condition to ward of a failure
--Checking of the existance of fields is a good idea
IF  OBJECT_ID('dbo.TableC') IS NOT NULL AND
    OBJECT_ID('tempdb..#TableC') IS NOT NULL
BEGIN

    INSERT INTO [TableC]
    SELECT [Id], [Value]
    FROM [#TableC]

END

在 DatabaseA 中,预部署脚本已经创建了#TableA。因此,由于数据库中没有dbo.TableB,因此不会执行数据准备块。 然而,数据转换将被执行,因为数据库中存在由"TableA -> TableB".

的转换块创建的#TableA。

在 DatabaseB 中,不会执行 "TableA -> TableB" 脚本的数据准备和转换块。然而,我们已经在 dbo.TableB 中有了转换后的数据。因此,"TableB -> TableC" 的数据准备和转换块将毫无问题地执行。

我在这种情况下使用以下解决方法

  1. 如果您想删除 table
  • 保留 dacpac(在 Tables 文件夹下)中的 table。
  • 创建 post 部署脚本以删除 table。
  1. 如果您想删除一列
  • 在 dacpac(在表文件夹下)中保留 table 定义中的列。
  • 创建一个 post 部署脚本来删除列。

通过这种方式,您可以从数据库中删除 table 和列,并且每当您进行下一次部署时(可能是几天甚至几个月后)从 dacpac 中排除 table/columns,以便 dacpac使用最新架构更新。