【问题标题】:DACPAC schema compare runs before pre-deployment scripts during publishDACPAC 模式比较在发布期间的预部署脚本之前运行
【发布时间】:2015-11-26 15:16:13
【问题描述】:

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

任何人都有不涉及发布两次的解决方法吗?

【问题讨论】:

  • 如果您使用重构功能,则应在本机处理重命名。项目中也支持删除表。然而,Ed 的建议在本机功能不起作用的情况下是有效的。您想删除/重命名/完成哪些需要在架构比较之前完成的操作?
  • 在这种特殊情况下,我删除了一个外键,将 FK 列从 BIGINT 更改为 INT,并将 FK 指向另一个本身正在重构的表。部署在第一次运行时失败,因为架构比较是在这些更改之前完成的。它在第二次运行时部署。

标签: sql-server visual-studio-2013 sql-server-2014 msdeploy sql-server-data-tools


【解决方案1】:

Gert Drapers 将其称为 pre-pre-deployment 脚本here

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

如果您是唯一的开发人员 - 这不是问题,但是当您有一个大型团队时,必须在每个数据库发布之前以某种方式执行“单独的脚本”。

我们使用的解决方法:

  • 创建具有属性 [Build action = None] 的单独 SQL“发布前”脚本(在 DB 项目中)
  • 创建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 调用“发布前”脚本。

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

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

【讨论】:

【解决方案2】:

从使用 Visual Studio 转变为使用驱动 sqlpackage.exe 的脚本,您可以在比较之前灵活地运行脚本:

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

编辑

【讨论】:

  • 您现有的 CI 流程如何调用 msdeploy?
【解决方案3】:

在部署数据库项目期间,我们遇到了需要将数据从一个表转换到另一个表的情况。当然,使用数据库项目是一个问题,因为在部署前目标表(列)仍然不存在,但在部署后脚本中源表(列)已经不存在。

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

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

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

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

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

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

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

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

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

让我向您展示这个想法如何在 DatabaseA 和 DatabaseB 中发挥作用。 假设 DB 项目有两对部署前和部署后脚本:“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

部署后脚本

--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”的数据准备和转换块将毫无问题地执行。

【讨论】:

    【解决方案4】:

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-05-09
      • 2014-12-21
      • 1970-01-01
      • 2018-08-28
      • 2017-07-16
      • 2017-12-05
      • 1970-01-01
      • 2020-05-19
      相关资源
      最近更新 更多