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,我们使用了以下想法(此方法可用于任何数据修改):
- 开发人员将目标 table (dbo.TableB) 添加到数据库项目并将其部署到本地数据库(无需提交到 SVN)
- 他或她创建了预部署转换脚本。诀窍是脚本将结果数据放入临时 table: #TableB
- 开发人员删除了数据库项目中的dbo.TableA。假定 table 将在主生成脚本执行期间被删除。
- 开发人员编写了一个 post 部署脚本,将数据从 #TableB 复制到刚刚由主脚本创建的 dbo.TableB。
- 所有更改都已提交到 SVN 中。
这样我们就不需要预先部署脚本,因为我们将中间数据存储在临时文件中 table。
我想说的是,使用预部署脚本的方法具有相同的中间(临时)数据,但它不是存储在临时 table 中,而是存储在真实 table秒。它发生在部署前和部署前之间。执行预部署脚本后,此中间数据消失。
更重要的是,使用临时 tables 的方法允许我们面对以下复杂但真实的情况:假设我们在我们的数据库项目中有两个转换:
- 表 A -> 表 B
- 表 B -> 表 C
除此之外,我们还有两个数据库:
- 具有表A的数据库A
- 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" 的数据准备和转换块将毫无问题地执行。
我在这种情况下使用以下解决方法
- 如果您想删除 table
- 保留 dacpac(在 Tables 文件夹下)中的 table。
- 创建 post 部署脚本以删除 table。
- 如果您想删除一列
- 在 dacpac(在表文件夹下)中保留 table 定义中的列。
- 创建一个 post 部署脚本来删除列。
通过这种方式,您可以从数据库中删除 table 和列,并且每当您进行下一次部署时(可能是几天甚至几个月后)从 dacpac 中排除 table/columns,以便 dacpac使用最新架构更新。
使用 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,我们使用了以下想法(此方法可用于任何数据修改):
- 开发人员将目标 table (dbo.TableB) 添加到数据库项目并将其部署到本地数据库(无需提交到 SVN)
- 他或她创建了预部署转换脚本。诀窍是脚本将结果数据放入临时 table: #TableB
- 开发人员删除了数据库项目中的dbo.TableA。假定 table 将在主生成脚本执行期间被删除。
- 开发人员编写了一个 post 部署脚本,将数据从 #TableB 复制到刚刚由主脚本创建的 dbo.TableB。
- 所有更改都已提交到 SVN 中。
这样我们就不需要预先部署脚本,因为我们将中间数据存储在临时文件中 table。
我想说的是,使用预部署脚本的方法具有相同的中间(临时)数据,但它不是存储在临时 table 中,而是存储在真实 table秒。它发生在部署前和部署前之间。执行预部署脚本后,此中间数据消失。
更重要的是,使用临时 tables 的方法允许我们面对以下复杂但真实的情况:假设我们在我们的数据库项目中有两个转换:
- 表 A -> 表 B
- 表 B -> 表 C
除此之外,我们还有两个数据库:
- 具有表A的数据库A
- 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" 的数据准备和转换块将毫无问题地执行。
我在这种情况下使用以下解决方法
- 如果您想删除 table
- 保留 dacpac(在 Tables 文件夹下)中的 table。
- 创建 post 部署脚本以删除 table。
- 如果您想删除一列
- 在 dacpac(在表文件夹下)中保留 table 定义中的列。
- 创建一个 post 部署脚本来删除列。
通过这种方式,您可以从数据库中删除 table 和列,并且每当您进行下一次部署时(可能是几天甚至几个月后)从 dacpac 中排除 table/columns,以便 dacpac使用最新架构更新。