Использование sqlcmd для выполнения сценариев развертывания, сгенерированных DAC в транзакции

У нас есть решение DACPAC (sqlproj), в котором есть несколько таблиц и сценарий после развертывания, который выполняет некоторые запросы DML. Если запрос DML завершается неудачно (я поднимаю ошибку со степенью серьезности =20), я бы хотел откатить все изменения, включая изменения DDL, сделанные dacpac, и изменения файла после развертывания. Это особенно полезно, когда я буду обновлять существующую целевую базу данных.

Я стремлюсь к атомарному обновлению БД при публикации DACPAC - все изменения DDL, упомянутые в решении DACPAC, должны публиковаться только тогда, когда все в сценарии после развертывания успешно выполнено.

Поскольку изменения DACPAC DDL фиксируются до того, как он запускает сценарий после развертывания, я подумал, что создание всех изменений DAC в виде одного файла сценария с использованием DacServices.GenerateDeployScript поможет. Не выглядит так прямо вперед.

Кто-нибудь пробовал что-то подобное (и не получилось / прошло)?

Я сталкиваюсь со многими проблемами, такими как... Создание / изменение базы данных не должно быть в транзакции. Откат не происходит вообще.

[Edit 10Nov]: Вставьте сценарий развертывания, сгенерированный dacpac, чтобы я мог лучше объяснить мою проблему (надеюсь)

/*
Deployment script for 9Nov

This code was generated by a tool.
Changes to this file may cause incorrect behavior and will be lost if
the code is regenerated.
*/

GO
SET ANSI_NULLS, ANSI_PADDING, ANSI_WARNINGS, ARITHABORT, CONCAT_NULL_YIELDS_NULL, QUOTED_IDENTIFIER ON;

SET NUMERIC_ROUNDABORT OFF;


GO
:setvar INSTALL_DIR "D:\EDW_9Nov\"
:setvar DatabaseName "9Nov"
:setvar DefaultFilePrefix "9Nov"
:setvar DefaultDataPath "C:\Program Files\Microsoft SQL Server\MSSQL12.MSSQLSERVER\MSSQL\DATA\"
:setvar DefaultLogPath "C:\Program Files\Microsoft SQL Server\MSSQL12.MSSQLSERVER\MSSQL\DATA\"

GO
:on error exit
GO
/*
Detect SQLCMD mode and disable script execution if SQLCMD mode is not supported.
To re-enable the script after enabling SQLCMD mode, execute the following:
SET NOEXEC OFF; 
*/
:setvar __IsSqlCmdEnabled "True"
GO
IF N'$(__IsSqlCmdEnabled)' NOT LIKE N'True'
    BEGIN
        PRINT N'SQLCMD mode must be enabled to successfully execute this script.';
        SET NOEXEC ON;
    END


GO
USE [master];


GO

IF (DB_ID(N'$(DatabaseName)') IS NOT NULL) 
BEGIN
    ALTER DATABASE [$(DatabaseName)]
    SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
    DROP DATABASE [$(DatabaseName)];
END

GO
PRINT N'Creating $(DatabaseName)...'
GO
CREATE DATABASE [$(DatabaseName)]
    ON 
    PRIMARY(NAME = [$(DatabaseName)], FILENAME = N'$(DefaultDataPath)$(DefaultFilePrefix)_Primary.mdf')
    LOG ON (NAME = [$(DatabaseName)_log], FILENAME = N'$(DefaultLogPath)$(DefaultFilePrefix)_Primary.ldf') COLLATE SQL_Latin1_General_CP1_CI_AS
GO
IF EXISTS (SELECT 1
           FROM   [master].[dbo].[sysdatabases]
           WHERE  [name] = N'$(DatabaseName)')
    BEGIN
        ALTER DATABASE [$(DatabaseName)]
            SET ANSI_NULLS ON,
                ANSI_PADDING ON,
                ANSI_WARNINGS ON,
                ARITHABORT ON,
                CONCAT_NULL_YIELDS_NULL ON,
                NUMERIC_ROUNDABORT OFF,
                QUOTED_IDENTIFIER ON,
                ANSI_NULL_DEFAULT ON,
                CURSOR_DEFAULT LOCAL,
                RECOVERY SIMPLE,
                CURSOR_CLOSE_ON_COMMIT OFF,
                AUTO_CREATE_STATISTICS ON,
                AUTO_SHRINK OFF,
                AUTO_UPDATE_STATISTICS ON,
                RECURSIVE_TRIGGERS OFF 
            WITH ROLLBACK IMMEDIATE;
        ALTER DATABASE [$(DatabaseName)]
            SET AUTO_CLOSE OFF 
            WITH ROLLBACK IMMEDIATE;
    END


GO
IF EXISTS (SELECT 1
           FROM   [master].[dbo].[sysdatabases]
           WHERE  [name] = N'$(DatabaseName)')
    BEGIN
        ALTER DATABASE [$(DatabaseName)]
            SET ALLOW_SNAPSHOT_ISOLATION OFF;
    END


GO
IF EXISTS (SELECT 1
           FROM   [master].[dbo].[sysdatabases]
           WHERE  [name] = N'$(DatabaseName)')
    BEGIN
        ALTER DATABASE [$(DatabaseName)]
            SET READ_COMMITTED_SNAPSHOT OFF 
            WITH ROLLBACK IMMEDIATE;
    END


GO
IF EXISTS (SELECT 1
           FROM   [master].[dbo].[sysdatabases]
           WHERE  [name] = N'$(DatabaseName)')
    BEGIN
        ALTER DATABASE [$(DatabaseName)]
            SET AUTO_UPDATE_STATISTICS_ASYNC OFF,
                PAGE_VERIFY NONE,
                DATE_CORRELATION_OPTIMIZATION OFF,
                DISABLE_BROKER,
                PARAMETERIZATION SIMPLE,
                SUPPLEMENTAL_LOGGING OFF 
            WITH ROLLBACK IMMEDIATE;
    END


GO
IF IS_SRVROLEMEMBER(N'sysadmin') = 1
    BEGIN
        IF EXISTS (SELECT 1
                   FROM   [master].[dbo].[sysdatabases]
                   WHERE  [name] = N'$(DatabaseName)')
            BEGIN
                EXECUTE sp_executesql N'ALTER DATABASE [$(DatabaseName)]
    SET TRUSTWORTHY OFF,
        DB_CHAINING OFF 
    WITH ROLLBACK IMMEDIATE';
            END
    END
ELSE
    BEGIN
        PRINT N'The database settings cannot be modified. You must be a SysAdmin to apply these settings.';
    END


GO
IF IS_SRVROLEMEMBER(N'sysadmin') = 1
    BEGIN
        IF EXISTS (SELECT 1
                   FROM   [master].[dbo].[sysdatabases]
                   WHERE  [name] = N'$(DatabaseName)')
            BEGIN
                EXECUTE sp_executesql N'ALTER DATABASE [$(DatabaseName)]
    SET HONOR_BROKER_PRIORITY OFF 
    WITH ROLLBACK IMMEDIATE';
            END
    END
ELSE
    BEGIN
        PRINT N'The database settings cannot be modified. You must be a SysAdmin to apply these settings.';
    END


GO
ALTER DATABASE [$(DatabaseName)]
    SET TARGET_RECOVERY_TIME = 0 SECONDS 
    WITH ROLLBACK IMMEDIATE;


GO
IF EXISTS (SELECT 1
           FROM   [master].[dbo].[sysdatabases]
           WHERE  [name] = N'$(DatabaseName)')
    BEGIN
        ALTER DATABASE [$(DatabaseName)]
            SET FILESTREAM(NON_TRANSACTED_ACCESS = OFF),
                CONTAINMENT = NONE 
            WITH ROLLBACK IMMEDIATE;
    END


GO
IF EXISTS (SELECT 1
           FROM   [master].[dbo].[sysdatabases]
           WHERE  [name] = N'$(DatabaseName)')
    BEGIN
        ALTER DATABASE [$(DatabaseName)]
            SET AUTO_CREATE_STATISTICS ON(INCREMENTAL = OFF),
                MEMORY_OPTIMIZED_ELEVATE_TO_SNAPSHOT = OFF,
                DELAYED_DURABILITY = DISABLED 
            WITH ROLLBACK IMMEDIATE;
    END


GO
USE [$(DatabaseName)];


GO
IF fulltextserviceproperty(N'IsFulltextInstalled') = 1
    EXECUTE sp_fulltext_database 'enable';


GO
PRINT N'Creating [EDW_INTERNAL]...';


GO
CREATE SCHEMA [EDW_INTERNAL]
    AUTHORIZATION [dbo];


GO
PRINT N'Creating [EDW_INTERNAL].[DB_VERSIONS]...';


GO
CREATE TABLE [EDW_INTERNAL].[DB_VERSIONS] (
    [ID]                     BIGINT       IDENTITY (1, 1) NOT NULL,
    [MODULE]                 VARCHAR (30) NOT NULL,
    [FROM_VERSION]           VARCHAR (20) NOT NULL,
    [TO_VERSION]             VARCHAR (20) NOT NULL,
    [UPGRADE_DML_APPLIED_YN] VARCHAR (1)  NOT NULL,
    CONSTRAINT [PK_DB_VERSIONS] PRIMARY KEY CLUSTERED ([MODULE] ASC, [FROM_VERSION] ASC, [TO_VERSION] ASC)
);


GO
PRINT N'Creating [EDW_INTERNAL].[DML_UPGR_SCRIPT_MASTER]...';


GO
CREATE TABLE [EDW_INTERNAL].[DML_UPGR_SCRIPT_MASTER] (
    [MODULE]               VARCHAR (30)   NOT NULL,
    [FROM_VERSION]         VARCHAR (20)   NOT NULL,
    [TO_VERSION]           VARCHAR (20)   NOT NULL,
    [APPLY_ORDER]          INT            NOT NULL,
    [UPGR_SCRIPT_FILEPATH] VARCHAR (1024) NOT NULL,
    CONSTRAINT [PK_DML_UPGR_SCRIPT_MASTER] PRIMARY KEY CLUSTERED ([APPLY_ORDER] ASC, [TO_VERSION] ASC, [FROM_VERSION] ASC, [MODULE] ASC)
);


GO
PRINT N'Creating unnamed constraint on [EDW_INTERNAL].[DB_VERSIONS]...';


GO
ALTER TABLE [EDW_INTERNAL].[DB_VERSIONS]
    ADD DEFAULT 'N' FOR [UPGRADE_DML_APPLIED_YN];


GO
PRINT N'Creating [EDW_INTERNAL].[UPGRADE_DML]...';


GO

CREATE PROCEDURE EDW_INTERNAL.UPGRADE_DML
    @Module VARCHAR(30)
AS
BEGIN

    DECLARE @Failure bit = 1;


    IF @Failure = 1
    BEGIN
        RAISERROR
        (N'One or more database upgrade query statements have failed. Please check the DML Upgrade Log table for details.',
        20, -- Severity.
        1 -- State
        ) WITH LOG;
    END

END
GO
/*
Post-Deployment Script Template                         
--------------------------------------------------------------------------------------
 This file contains SQL statements that will be appended to the build script.       
 Use SQLCMD syntax to include a file in the post-deployment script.         
 Example:      :r .\myfile.sql                              
 Use SQLCMD syntax to reference a variable in the post-deployment script.       
 Example:      :setvar TableName MyTable                            
               SELECT * FROM [$(TableName)]                 
--------------------------------------------------------------------------------------
*/

-- Reference to load the Version Upgrade tables with rows
--:r VersionUpgradeRowsPopulate.sql

-- execute
EXEC    [EDW_INTERNAL].[UPGRADE_DML] @MODULE = 'Test_Common'
GO

GO
DECLARE @VarDecimalSupported AS BIT;

SELECT @VarDecimalSupported = 0;

IF ((ServerProperty(N'EngineEdition') = 3)
    AND (((@@microsoftversion / power(2, 24) = 9)
          AND (@@microsoftversion & 0xffff >= 3024))
         OR ((@@microsoftversion / power(2, 24) = 10)
             AND (@@microsoftversion & 0xffff >= 1600))))
    SELECT @VarDecimalSupported = 1;

IF (@VarDecimalSupported > 0)
    BEGIN
        EXECUTE sp_db_vardecimal_storage_format N'$(DatabaseName)', 'ON';
    END


GO
PRINT N'Update complete.';


GO

1 ответ

Из этого вопроса я понял, что вы знаете, как включить откат для основного сценария, поэтому чтобы выполнить откат до / после сценариев, вам нужно сделать что-то подобное (просто быстрые тесты, так что это может быть не идеальным решением). Публикуйте скрипт 2 раза, второй раз он должен завершиться сбоем, так как таблица уже существует:

Предварительный сценарий:

IF (SELECT OBJECT_ID('tempdb..#tmpErrors2')) IS NOT NULL DROP TABLE #tmpErrors
GO
CREATE TABLE #tmpErrors2 (Error int)
GO
SET XACT_ABORT ON
GO
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
GO
BEGIN TRANSACTION
GO



--Your prescript part goes here



GO
IF @@ERROR <> 0
   AND @@TRANCOUNT > 0
    BEGIN
        ROLLBACK;
    END

IF @@TRANCOUNT = 0
    BEGIN
        INSERT  INTO #tmpErrors2 (Error)
        VALUES                 (1);
        BEGIN TRANSACTION;
    END

Post script:

CREATE TABLE tttt4 (a int);
GO
IF @@ERROR <> 0
   AND @@TRANCOUNT > 0
    BEGIN
        ROLLBACK;
    END

IF @@TRANCOUNT = 0
    BEGIN
        INSERT  INTO #tmpErrors2 (Error)
        VALUES                 (1);
        BEGIN TRANSACTION;
    END


GO

IF EXISTS (SELECT * FROM #tmpErrors2) ROLLBACK TRANSACTION
GO
IF @@TRANCOUNT>0 BEGIN
PRINT N'Post/Pre script finished'
COMMIT TRANSACTION
END
ELSE PRINT N'Pre/Post scripts failed'
GO
DROP TABLE #tmpErrors2
GO
Другие вопросы по тегам