Поделиться через


Пошаговое руководство. Расширение процесса развертывания проекта базы данных для изменения плана развертывания

Можно создать участников развертывания для выполнения специализированных действий при развертывании проекта SQL. Вы можете создать либо DeploymentPlanModifier, либо DeploymentPlanExecutor. Используйте DeploymentPlanModifier для изменения плана до его выполнения и DeploymentPlanExecutor для осуществления операций в ходе выполнения плана. В этом пошаговом руководстве вы создадите DeploymentPlanModifier с именем SqlRestartableScriptContributor. DeploymentPlanModifier SqlRestartableScriptContributor добавляет инструкции IF в пакеты скрипта развертывания, чтобы можно было повторять выполнение скрипта, пока пакеты не будут завершены в случае возникновения ошибки во время выполнения.

В этом пошаговом руководстве выполняются следующие основные задачи:

Предварительные условия

Для выполнения этого пошагового руководства требуются следующие компоненты:

  • Необходимо установить версию Visual Studio, которая включает sql Server Data Tools и поддерживает разработку C#.

  • Необходимо иметь проект SQL, который содержит объекты SQL.

  • Экземпляр SQL Server, на котором можно развернуть проект базы данных.

Примечание.

Это пошаговое руководство предназначено для пользователей, уже знакомых с функциями SQL пакета SQL Server Data Tools. Предполагается также знакомство с основными средствами Visual Studio, такими как создание библиотеки классов и использование редактора кода для добавления кода к классу.

Создание участника развертывания

Для создания участника развертывания необходимо выполнить следующие задачи:

  • Создать проект библиотеки классов и добавить необходимые ссылки.

  • Определить класс с именем SqlRestartableScriptContributor, который наследует от DeploymentPlanModifier.

  • Переопределить метод OnExecute.

  • Добавить закрытые вспомогательные методы.

  • Построить результирующую сборку.

Создание проекта библиотеки классов

  1. Создайте проект библиотеки классов C# (платформа .NET Framework) с именем MyOtherDeploymentContributor.

  2. Переименуйте файл Class1.cs в SqlRestartableScriptContributor.cs.

  3. В обозревателе решений щелкните правой кнопкой мыши узел проекта, затем выберите команду Добавить ссылку.

  4. Выберите System.ComponentModel.Composition на вкладке "Платформы".

  5. В меню "Проект" выберите параметр "Управление пакетами NuGet". Установите последние стабильные выпуски для Microsoft.SqlServer.DacFx.

После этого приступите к добавлению кода к классу.

Чтобы определить класс SqlRestartableScriptContributor

  1. В редакторе кода обновите файл class1.cs для согласования со следующими используемыми инструкциями:

    using System;  
    using System.Collections.Generic;  
    using System.Globalization;  
    using System.Text;  
    using Microsoft.SqlServer.Dac.Deployment;  
    using Microsoft.SqlServer.Dac.Model;  
    using Microsoft.SqlServer.TransactSql.ScriptDom;  
    
  2. Обновите определение класса для согласования со следующим примером:

        /// <summary>  
    /// This deployment contributor modifies a deployment plan by adding if statements  
    /// to the existing batches in order to make a deployment script able to be rerun to completion  
    /// if an error is encountered during execution  
    /// </summary>  
    [ExportDeploymentPlanModifier("MyOtherDeploymentContributor.RestartableScriptContributor", "1.0.0.0")]  
    public class SqlRestartableScriptContributor : DeploymentPlanModifier  
    {  
    }  
    
    

    Теперь вы определили участника развертывания, который наследует от DeploymentPlanModifier. В процессе сборки и развертывания кастомные компоненты подключаются из стандартного каталога расширений. Компоненты, изменяющие план развертывания, определяются с помощью атрибута ExportDeploymentPlanModifier. Этот атрибут требуется для обнаружения участников. Этот атрибут должен выглядеть аналогично следующему декоратору функции:

    [ExportDeploymentPlanModifier("MyOtherDeploymentContributor.RestartableScriptContributor", "1.0.0.0")]  
    
    
  3. Добавьте следующие объявления членов:

         private const string BatchIdColumnName = "BatchId";  
            private const string DescriptionColumnName = "Description";  
    
            private const string CompletedBatchesVariableName = "CompletedBatches";  
            private const string CompletedBatchesVariable = "$(CompletedBatches)";  
            private const string CompletedBatchesSqlCmd = @":setvar " + CompletedBatchesVariableName + " __completedBatches_{0}_{1}";  
            private const string TotalBatchCountSqlCmd = @":setvar TotalBatchCount {0}";  
            private const string CreateCompletedBatchesTable = @"  
    if OBJECT_ID(N'tempdb.dbo." + CompletedBatchesVariable + @"', N'U') is null  
    begin  
    use tempdb  
    create table [dbo].[$(CompletedBatches)]  
    (  
    BatchId int primary key,  
    Description nvarchar(300)  
    )  
    use [$(DatabaseName)]  
    end  
    ";  
    
    

    Затем необходимо переопределить метод OnExecute для добавления кода, который должен быть выполнен при развертывании проекта базы данных.

Переопределить метод OnExecute

  1. Добавьте следующий метод в ваш класс SqlRestartableScriptContributor:

    /// <summary>  
    /// You override the OnExecute method to do the real work of the contributor.  
    /// </summary>  
    /// <param name="context"></param>  
    protected override void OnExecute(DeploymentPlanContributorContext context)  
    {  
         // Replace this with the method body  
    }  
    
    

    Вы переопределяете метод OnExecute из базового класса DeploymentPlanContributor. DeploymentPlanContributor — это базовый класс для DeploymentPlanModifier и DeploymentPlanExecutor. Объект DeploymentPlanContributorContext передается методу OnExecute, предоставляя доступ к любым указанным аргументам, модели источника и базы данных целевого объекта, плана развертывания и параметров развертывания. В этом примере мы получаем план развертывания и имя целевой базы данных.

  2. Перейдем к добавлению начала текста в метод OnExecute:

    // Obtain the first step in the Plan from the provided context  
    DeploymentStep nextStep = context.PlanHandle.Head;  
    int batchId = 0;  
    BeginPreDeploymentScriptStep beforePreDeploy = null;  
    
    // Loop through all steps in the deployment plan  
    while (nextStep != null)  
    {  
        // Increment the step pointer, saving both the current and next steps  
        DeploymentStep currentStep = nextStep;  
        nextStep = currentStep.Next;  
    
        // Add additional step processing here  
    }  
    
    // if we found steps that required processing, set up a temporary table to track the work that you are doing  
    if (beforePreDeploy != null)  
    {  
        // Add additional post-processing here  
    }  
    
    // Cleanup and drop the table   
    DeploymentScriptStep dropStep = new DeploymentScriptStep(DropCompletedBatchesTable);  
    base.AddAfter(context.PlanHandle, context.PlanHandle.Tail, dropStep);  
    
    

    В этом коде мы определим несколько локальных переменных и настроим цикл, который обрабатывает обработку всех шагов в плане развертывания. После завершения цикла необходимо выполнить некоторую последующую обработку и удалить временную таблицу, созданную во время развертывания для отслеживания хода выполнения плана. Основными применяемыми здесь типами являются DeploymentStep и DeploymentScriptStep. Основной метод — AddAfter.

  3. Теперь добавим дополнительный шаг обработки вместо комментария с текстом «Ввести дополнительный шаг обработки здесь»:

    // Look for steps that mark the pre/post deployment scripts  
    // These steps will always be in the deployment plan even if the  
    // user's project does not have a pre/post deployment script  
    if (currentStep is BeginPreDeploymentScriptStep)  
    {  
        // This step marks the beginning of the predeployment script.  
        // Save the step and move on.  
        beforePreDeploy = (BeginPreDeploymentScriptStep)currentStep;  
        continue;  
    }  
    if (currentStep is BeginPostDeploymentScriptStep)  
    {  
        // This is the step that marks the beginning of the post deployment script.    
        // We do not continue processing after this point.  
        break;  
    }  
    if (currentStep is SqlPrintStep)  
    {  
        // We do not need to put if statements around these  
        continue;  
    }  
    
    // if we have not yet found the beginning of the pre-deployment script steps,   
    // skip to the next step.  
    if (beforePreDeploy == null)  
    {  
        // We only surround the "main" statement block with conditional  
        // statements  
        continue;  
    }  
    
    // Determine if this is a step that we need to surround with a conditional statement  
    DeploymentScriptDomStep domStep = currentStep as DeploymentScriptDomStep;  
    if (domStep == null)  
    {  
        // This step is not a step that we know how to modify,  
        // so skip to the next step.  
        continue;  
    }  
    
    TSqlScript script = domStep.Script as TSqlScript;  
    if (script == null)  
    {  
        // The script dom step does not have a script with batches - skip  
        continue;  
    }  
    
        // Loop through all the batches in the script for this step.  All the statements  
        // in the batch will be enclosed in an if statement that will check the  
        // table to ensure that the batch has not already been executed  
        TSqlObject sqlObject;  
        string stepDescription;  
        GetStepInfo(domStep, out stepDescription, out sqlObject);  
        int batchCount = script.Batches.Count;  
    
    for (int batchIndex = 0; batchIndex < batchCount; batchIndex++)  
    {  
        // Add batch processing here  
    }  
    
    

    Комментарии в коде поясняют выполняемую обработку. На общем уровне, этот код ищет шаги, которые вас интересуют, пропуская другие и останавливаясь при достижении начала этапов после развертывания. Если шаг содержит инструкции, которые необходимо окружать условными условиями, мы выполняем дополнительную обработку. Ключевые типы, методы и свойства включают следующие компоненты из библиотеки DacFx: BeginPreDeploymentScriptStep, BeginPostDeploymentScriptStep, TSqlObject, TSqlScript, Script, DeploymentScriptDomStep и SqlPrintStep.

  4. Теперь добавим код пакетной обработки вместо комментария с текстом «Ввести пакетную обработку здесь»:

        // Create the if statement that will contain the batch's contents  
        IfStatement ifBatchNotExecutedStatement = CreateIfNotExecutedStatement(batchId);  
        BeginEndBlockStatement statementBlock = new BeginEndBlockStatement();  
        ifBatchNotExecutedStatement.ThenStatement = statementBlock;  
        statementBlock.StatementList = new StatementList();  
    
        TSqlBatch batch = script.Batches[batchIndex];  
        int statementCount = batch.Statements.Count;  
    
        // Loop through all statements in the batch, embedding those in an sp_execsql  
        // statement that must be handled this way (schemas, stored procedures,   
        // views, functions, and triggers).  
        for (int statementIndex = 0; statementIndex < statementCount; statementIndex++)  
        {  
            // Add additional statement processing here  
        }  
    
        // Add an insert statement to track that all the statements in this  
        // batch were executed.  Turn on nocount to improve performance by  
        // avoiding row inserted messages from the server  
        string batchDescription = string.Format(CultureInfo.InvariantCulture,  
            "{0} batch {1}", stepDescription, batchIndex);  
    
        PredicateSetStatement noCountOff = new PredicateSetStatement();  
        noCountOff.IsOn = false;  
        noCountOff.Options = SetOptions.NoCount;  
    
        PredicateSetStatement noCountOn = new PredicateSetStatement();  
        noCountOn.IsOn = true;  
        noCountOn.Options = SetOptions.NoCount;   
        InsertStatement batchCompleteInsert = CreateBatchCompleteInsert(batchId, batchDescription);  
        statementBlock.StatementList.Statements.Add(noCountOn);  
    statementBlock.StatementList.Statements.Add(batchCompleteInsert);  
        statementBlock.StatementList.Statements.Add(noCountOff);  
    
        // Remove all the statements from the batch (they are now in the if block) and add the if statement  
        // as the sole statement in the batch  
        batch.Statements.Clear();  
        batch.Statements.Add(ifBatchNotExecutedStatement);  
    
        // Next batch  
        batchId++;  
    
    

    В этом коде создается инструкция IF с блоком BEGIN/END. Затем мы выполняем дополнительную обработку утверждений в пакете. После ее завершения добавляется инструкция INSERT для ввода информации во временную таблицу, которая отслеживает ход выполнения скрипта. Наконец, обновим пакет, заменив инструкции, которые были там раньше, на новую инструкцию IF, содержащую эти инструкции. Основные типы, методы и свойства включают в себя IfStatement, BeginEndBlockStatement, StatementList, TSqlBatch, PredicateSetStatement, SetOptions и InsertStatement.

  5. Теперь добавьте тело цикла обработки утверждений. Замените комментарий с текстом «Введите дополнительную обработку инструкции здесь»:

    TSqlStatement smnt = batch.Statements[statementIndex];  
    
    if (IsStatementEscaped(sqlObject))  
    {  
        // "escape" this statement by embedding it in a sp_executesql statement  
        string statementScript;  
        domStep.ScriptGenerator.GenerateScript(smnt, out statementScript);  
        ExecuteStatement spExecuteSql = CreateExecuteSql(statementScript);  
        smnt = spExecuteSql;  
    }  
    
    statementBlock.StatementList.Statements.Add(smnt);  
    
    

    Применительно к каждой инструкции в пакете, если эта инструкция относится к типу, требующему заключения в инструкцию sp_executesql, внесите в эту инструкцию соответствующие изменения. Затем в коде добавляется инструкция к списку инструкций, относящемуся к созданному блоку BEGIN/END. Основные типы, методы и свойства включают TSqlStatement и ExecuteStatement.

  6. Наконец, добавьте раздел для последующей обработки вместо комментария с текстом «Введите дополнительную последующую обработку здесь»:

    // Declare a SqlCmd variables.  
    //  
    // CompletedBatches variable - defines the name of the table in tempdb that will track  
    // all the completed batches.  The temporary table's name has the target database name and  
    // a guid embedded in it so that:  
    // * Multiple deployment scripts targeting different DBs on the same server  
    // * Failed deployments with old tables do not conflict with more recent deployments  
    //  
    // TotalBatchCount variable - the total number of batches surrounded by if statements.  Using this  
    // variable pre/post deployment scripts can also use the CompletedBatches table to make their  
    // script rerunnable if there is an error during execution  
    StringBuilder sqlcmdVars = new StringBuilder();  
    sqlcmdVars.AppendFormat(CultureInfo.InvariantCulture, CompletedBatchesSqlCmd,  
        context.Options.TargetDatabaseName, Guid.NewGuid().ToString("D"));  
    sqlcmdVars.AppendLine();  
    sqlcmdVars.AppendFormat(CultureInfo.InvariantCulture, TotalBatchCountSqlCmd, batchId);  
    
    DeploymentScriptStep completedBatchesSetVarStep = new DeploymentScriptStep(sqlcmdVars.ToString());  
    base.AddBefore(context.PlanHandle, beforePreDeploy, completedBatchesSetVarStep);  
    
    // Create the temporary table we will use to track the work that we are doing  
    DeploymentScriptStep createStatusTableStep = new DeploymentScriptStep(CreateCompletedBatchesTable);  
    base.AddBefore(context.PlanHandle, beforePreDeploy, createStatusTableStep);  
    
    

    Если в ходе обработки обнаруживаются один или несколько шагов, заключенных в условные операторы, необходимо добавить в скрипт развертывания инструкции задания переменных SQLCMD. Используются следующие переменные:

    • CompletedBatches содержит уникальное имя временной таблицы, которую скрипт развертывания использует для отслеживания успешного завершения пакетов при выполнении скрипта.
    • TotalBatchCount содержит общее количество пакетов в скрипте развертывания.

    К другим типам, свойствам и методам, интересующим вас, относятся:

    StringBuilder, DeploymentScriptStep и AddBefore.

    Затем необходимо определить вспомогательные методы, вызываемые этим методом.

Добавление вспомогательных методов

  • Может быть определен целый ряд вспомогательных методов. К важным методам относятся следующие.

    Method Description
    CreateExecuteSQL Определите метод CreateExecuteSQL, чтобы обернуть предоставленное выражение в инструкцию EXEC sp_executesql. Ключевые типы, методы и свойства включают следующие компоненты API DacFx: ExecuteStatement, ExecuteableProcedureReference, SchemaObjectName, ProcedureReference и ExecuteParameter.
    CreateCompletedBatchesName Определение метода CreateCompletedBatchesName. Этот метод создает имя, вставленное во временную таблицу для пакета. Ключевые типы, методы и свойства включают следующие компоненты API DacFx: SchemaObjectName.
    ПредложениеЭкранировано Определите метод IsStatementEscaped. Этот метод определяет, относится ли элемент модели к такому типу, который требует заключения инструкции в инструкцию EXEC sp_executesql, прежде чем ее можно будет включить в инструкцию IF. Ключевые типы, методы и свойства включают следующие компоненты API DacFx: TSqlObject.ObjectType, ModelTypeClass и свойство TypeClass для следующих типов моделей: Schema, Procedure, View, TableValuedFunction, ScalarFunction, DatabaseDdlTrigger, DmlTrigger, ServerDdlTrigger.
    CreateBatchCompleteInsert Определение метода CreateBatchCompleteInsert. Этот метод создает инструкцию INSERT, которая добавляется в скрипт развертывания для отслеживания хода выполнения скрипта. Ключевые типы, методы и свойства включают следующие компоненты API DacFx: InsertStatement, NamedTableReference, ColumnReferenceExpression, ValuesInsertSource и RowValue.
    СоздатьЕслиНеВыполненоУтверждение Определите метод CreateIfNotExecutedStatement. Этот метод создает инструкцию IF для проверки того, показывают ли результаты в таблице выполнения временных пакетов, что этот пакет уже выполнялся. К основным типам, методам и свойствам относятся IfStatement, ExistsPredicate, ScalarSubquery, NamedTableReference, WhereClause, ColumnReferenceExpression, IntegerLiteral, BooleanComparisonExpression и BooleanNotExpression.
    GetStepInfo Определение метода GetStepInfo. Этот метод извлекает сведения об элементе модели, используемом для создания скрипта данного шага, в дополнение к имени шага. Типы и методы, представляющие интерес, включают DeploymentPlanContributorContext, DeploymentScriptDomStep, TSqlObject, CreateElementStep, AlterElementStep и DropElementStep.
    ПолучитьНазваниеЭлемента Создает отформатированное имя для TSqlObject.
  1. Добавьте следующий код для определения вспомогательных методов:

    
    /// <summary>  
    /// The CreateExecuteSql method "wraps" the provided statement script in an "sp_executesql" statement  
    /// Examples of statements that must be so wrapped include: stored procedures, views, and functions  
    /// </summary>  
    private static ExecuteStatement CreateExecuteSql(string statementScript)  
    {  
        // define a new Exec statement  
        ExecuteStatement executeSp = new ExecuteStatement();  
        ExecutableProcedureReference spExecute = new ExecutableProcedureReference();  
        executeSp.ExecuteSpecification = new ExecuteSpecification { ExecutableEntity = spExecute };  
    
        // define the name of the procedure that you want to execute, in this case sp_executesql  
        SchemaObjectName procName = new SchemaObjectName();  
        procName.Identifiers.Add(CreateIdentifier("sp_executesql", QuoteType.NotQuoted));  
        ProcedureReference procRef = new ProcedureReference { Name = procName };  
    
        spExecute.ProcedureReference = new ProcedureReferenceName { ProcedureReference = procRef };  
    
        // add the script parameter, constructed from the provided statement script  
        ExecuteParameter scriptParam = new ExecuteParameter();  
        spExecute.Parameters.Add(scriptParam);  
        scriptParam.ParameterValue = new StringLiteral { Value = statementScript };  
        scriptParam.Variable = new VariableReference { Name = "@stmt" };  
        return executeSp;  
    }  
    
    /// <summary>  
    /// The CreateIdentifier method returns a Identifier with the specified value and quoting type  
    /// </summary>  
    private static Identifier CreateIdentifier(string value, QuoteType quoteType)  
    {  
        return new Identifier { Value = value, QuoteType = quoteType };  
    }  
    
    /// <summary>  
    /// The CreateCompletedBatchesName method creates the name that will be inserted  
    /// into the temporary table for a batch.  
    /// </summary>  
    private static SchemaObjectName CreateCompletedBatchesName()  
    {  
        SchemaObjectName name = new SchemaObjectName();  
        name.Identifiers.Add(CreateIdentifier("tempdb", QuoteType.SquareBracket));  
        name.Identifiers.Add(CreateIdentifier("dbo", QuoteType.SquareBracket));  
        name.Identifiers.Add(CreateIdentifier(CompletedBatchesVariable, QuoteType.SquareBracket));  
        return name;  
    }  
    
    /// <summary>  
    /// Helper method that determines whether the specified statement needs to  
    /// be escaped  
    /// </summary>  
    /// <param name="sqlObject"></param>  
    /// <returns></returns>  
    private static bool IsStatementEscaped(TSqlObject sqlObject)  
    {  
        HashSet<ModelTypeClass> escapedTypes = new HashSet<ModelTypeClass>  
        {  
            Schema.TypeClass,  
            Procedure.TypeClass,  
            View.TypeClass,  
            TableValuedFunction.TypeClass,  
            ScalarFunction.TypeClass,  
            DatabaseDdlTrigger.TypeClass,  
            DmlTrigger.TypeClass,  
            ServerDdlTrigger.TypeClass  
        };  
        return escapedTypes.Contains(sqlObject.ObjectType);  
    }  
    
    /// <summary>  
    /// Helper method that creates an INSERT statement to track a batch being completed  
    /// </summary>  
    /// <param name="batchId"></param>  
    /// <param name="batchDescription"></param>  
    /// <returns></returns>  
    private static InsertStatement CreateBatchCompleteInsert(int batchId, string batchDescription)  
    {  
        InsertStatement insert = new InsertStatement();  
        NamedTableReference batchesCompleted = new NamedTableReference();  
        insert.InsertSpecification = new InsertSpecification();  
        insert.InsertSpecification.Target = batchesCompleted;  
        batchesCompleted.SchemaObject = CreateCompletedBatchesName();  
    
        // Build the columns inserted into  
        ColumnReferenceExpression batchIdColumn = new ColumnReferenceExpression();  
        batchIdColumn.MultiPartIdentifier = new MultiPartIdentifier();  
        batchIdColumn.MultiPartIdentifier.Identifiers.Add(CreateIdentifier(BatchIdColumnName, QuoteType.NotQuoted));  
    
        ColumnReferenceExpression descriptionColumn = new ColumnReferenceExpression();  
        descriptionColumn.MultiPartIdentifier = new MultiPartIdentifier();  
        descriptionColumn.MultiPartIdentifier.Identifiers.Add(CreateIdentifier(DescriptionColumnName, QuoteType.NotQuoted));  
    
        insert.InsertSpecification.Columns.Add(batchIdColumn);  
        insert.InsertSpecification.Columns.Add(descriptionColumn);  
    
        // Build the values inserted  
        ValuesInsertSource valueSource = new ValuesInsertSource();  
        insert.InsertSpecification.InsertSource = valueSource;  
    
        RowValue values = new RowValue();  
        values.ColumnValues.Add(new IntegerLiteral { Value = batchId.ToString() });  
        values.ColumnValues.Add(new StringLiteral { Value = batchDescription });  
        valueSource.RowValues.Add(values);  
    
        return insert;  
    }  
    
    /// <summary>  
    /// This is a helper method that generates an if statement that checks the batches executed  
    /// table to see if the current batch has been executed.  The if statement will look like this  
    ///   
    /// if not exists(select 1 from [tempdb].[dbo].[$(CompletedBatches)]   
    ///                where BatchId = batchId)  
    /// begin  
    /// end  
    /// </summary>  
    /// <param name="batchId"></param>  
    /// <returns></returns>  
    private static IfStatement CreateIfNotExecutedStatement(int batchId)  
    {  
        // Create the exists/select statement  
        ExistsPredicate existsExp = new ExistsPredicate();  
        ScalarSubquery subQuery = new ScalarSubquery();  
        existsExp.Subquery = subQuery;  
    
        subQuery.QueryExpression = new QuerySpecification  
        {  
            SelectElements =  
            {  
                new SelectScalarExpression  { Expression = new IntegerLiteral { Value ="1" } }  
            },  
            FromClause = new FromClause  
            {  
                TableReferences =  
                    {  
                        new NamedTableReference() { SchemaObject = CreateCompletedBatchesName() }  
                    }  
            },  
            WhereClause = new WhereClause  
            {  
                SearchCondition = new BooleanComparisonExpression  
                {  
                    ComparisonType = BooleanComparisonType.Equals,  
                    FirstExpression = new ColumnReferenceExpression  
                    {  
                        MultiPartIdentifier = new MultiPartIdentifier  
                        {  
                            Identifiers = { CreateIdentifier(BatchIdColumnName, QuoteType.SquareBracket) }  
                        }  
                    },  
                    SecondExpression = new IntegerLiteral { Value = batchId.ToString() }  
                }  
            }  
        };  
    
        // Put together the rest of the statement  
        IfStatement ifNotExists = new IfStatement  
        {  
            Predicate = new BooleanNotExpression  
            {  
                Expression = existsExp  
            }  
        };  
    
        return ifNotExists;  
    }  
    
    /// <summary>  
    /// Helper method that generates a useful description of the step.  
    /// </summary>  
    private static void GetStepInfo(  
        DeploymentScriptDomStep domStep,  
        out string stepDescription,  
        out TSqlObject element)  
    {  
        element = null;  
    
        // figure out what type of step we've got, and retrieve  
        // either the source or target element.  
        if (domStep is CreateElementStep)  
        {  
            element = ((CreateElementStep)domStep).SourceElement;  
        }  
        else if (domStep is AlterElementStep)  
        {  
            element = ((AlterElementStep)domStep).SourceElement;  
        }  
        else if (domStep is DropElementStep)  
        {  
            element = ((DropElementStep)domStep).TargetElement;  
        }  
    
        // construct the step description by concatenating the type and the fully qualified  
        // name of the associated element.  
        string stepTypeName = domStep.GetType().Name;  
        if (element != null)  
        {  
            string elementName = GetElementName(element);  
    
            stepDescription = string.Format(CultureInfo.InvariantCulture, "{0} {1}",  
                stepTypeName, elementName);  
        }  
        else  
        {  
            // if the step has no associated element, just use the step type as the description  
            stepDescription = stepTypeName;  
        }  
    }  
    
    private static string GetElementName(TSqlObject element)  
    {  
        StringBuilder name = new StringBuilder();  
        if (element.Name.HasExternalParts)  
        {  
            foreach (string part in element.Name.ExternalParts)  
            {  
                if (name.Length > 0)  
                {  
                    name.Append('.');  
                }  
                name.AppendFormat("[{0}]", part);  
            }  
        }  
    
        foreach (string part in element.Name.Parts)  
        {  
            if (name.Length > 0)  
            {  
                name.Append('.');  
            }  
            name.AppendFormat("[{0}]", part);  
        }  
    
        return name.ToString();  
    }  
    
    
  2. Сохраните изменения в SqlRestartableScriptContributor.cs.

Затем необходимо выполнить сборку библиотеки классов.

Для подписи и сборки приложения

  1. В меню Проект щелкните Свойства MyOtherDeploymentContributor.

  2. Откройте вкладку Подписание .

  3. Щелкните Подписать сборку.

  4. В окне Choose a strong name key file (Выбор файла ключа строгого имени) щелкните <New> (Создать).

  5. В диалоговом окне Создать ключ со строгим именем в поле Имя файла ключавведите MyRefKey.

  6. (Необязательно) Можно указать пароль для файла ключа строгого имени.

  7. Щелкните OK.

  8. В меню File (Файл) выберите команду Save All (Сохранить все).

  9. В меню Сборка выберите Построить решение.

    Затем необходимо установить сборку, чтобы она загружалась при развертывании проектов SQL.

Установка компонента развертывания

Для установки участника развертывания необходимо скопировать сборку и связанный файл .pdb в папку Расширения.

Чтобы установить сборку MyOtherDeploymentContributor

  1. Затем необходимо будет скопировать данные сборки в каталог Extensions. При запуске Visual Studio 2022 он определит все расширения в каталоге %Program Files%\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\Extensions\Microsoft\SQLDB\DAC directory и подкаталогах, а также сделать их доступными для использования.

  2. Скопируйте файл сборки MyOtherDeploymentContributor.dll из выходного каталога в каталог %Program Files%\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\Extensions\Microsoft\SQLDB\DAC. По умолчанию путь к скомпилированному файлу библиотеки DLL является ПутьКВашемуРешению\ПутьКВашемуПроекту\bin\Debug или ПутьКВашемуРешению\ПутьКВашемуПроекту\bin\Release.

Выполнение или тестирование применяемого участника развертывания

Для запуска и тестирования вашего участника развертывания вам необходимо выполнить следующие задачи.

  • Добавьте свойства в файл .sqlproj, который вы планируете собирать.

  • Развернуть проект базы данных с использованием MSBuild и предоставить соответствующие параметры.

Добавление свойств к файлу проекта SQL (SQLPROJ)

Всегда необходимо обновлять файл проекта SQL, чтобы указать идентификаторы участников, которых вы хотите запустить. Вы можете обновить проект SQL одним из двух способов:

  1. Можно изменить файл SQLPROJ вручную для добавления требуемых аргументов. Этот вариант может быть выбран, если у вашего модуля сборки нет аргументов, необходимых для конфигурирования, или если вы не собираетесь использовать его повторно в большом количестве проектов. Если вы выберете этот вариант, добавьте следующие инструкции в файл .sqlproj после первого узла Import в нем.

    <PropertyGroup>  
      <DeploymentContributors>  
        $(DeploymentContributors); MyOtherDeploymentContributor.RestartableScriptContributor  
      </DeploymentContributors>  
    </PropertyGroup>  
    
  2. Второй метод заключается в создании файла targets, содержащего необходимые аргументы контрибьютора. Это будет полезно, если один и тот же участник используется в нескольких проектах и если требуются аргументы участника, поскольку он включает значения по умолчанию. В этом случае создайте целевой файл в пути расширений MSBuild:

    1. Перейдите в каталог %Program Files%\MSBuild.

    2. Создайте папку MyContributors, в которой хранятся файлы целевых объектов.

    3. Создайте в этом каталоге файл MyContributors.targets, введите в него следующий текст и сохраните файл:

      <?xml version="1.0" encoding="utf-8"?>  
      
      <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">  
        <PropertyGroup>  
          <DeploymentContributors>$(DeploymentContributors);MyOtherDeploymentContributor.RestartableScriptContributor</DeploymentContributors>  
        </PropertyGroup>  
      </Project>  
      
      
    4. В файле SQLPROJ каждого проекта, в котором вы хотите запускать участников, импортируйте файл TARGETS, добавив следующую инструкцию в файл SQLPROJ после узла <Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\SSDT\Microsoft.Data.Tools.Schema.SqlTasks.targets" />:

      <Import Project="$(MSBuildExtensionsPath)\MyContributors\MyContributors.targets " />  
      
      

После следования одному из этих подходов можно использовать MSBuild для передачи параметров для сборки из командной строки.

Примечание.

Необходимо всегда обновлять свойство "DeploymentContributors", чтобы указать идентификатор участника. Это тот же идентификатор, который используется в атрибуте "ExportDeploymentPlanModifier" в вашем исходном файле участника. Без этого ваш компонент не будет запускаться при сборке проекта. Свойство ContributorArguments необходимо обновлять, только если у вас есть аргументы, необходимые для запуска вашего участника.

Развертывание проекта базы данных

Развертывание проекта SQL и создание отчета о развертывании

  • Проект может быть опубликован или развернут как обычно в Visual Studio. Достаточно открыть решение, содержащее проект SQL, и выбрать параметр "Опубликовать…" в открывающемся щелчком правой кнопкой мыши контекстном меню проекта или воспользоваться клавишей F5 для отладки развертывания в LocalDB. В этом примере мы используем "Опубликовать..." диалоговое окно для создания скрипта развертывания.

    1. Откройте Visual Studio и вызовите решение, содержащее проект SQL.

    2. Щелкните правой кнопкой мыши проект в обозревателе решений и выберите параметр Опубликовать....

    3. Укажите имя сервера и имя базы данных для публикации.

    4. Выберите Создать скрипт в параметрах в нижней части диалогового окна. Это действие создает скрипт, который можно использовать для развертывания. Мы можем проверить этот скрипт, чтобы убедиться, что наши инструкции IF были добавлены, чтобы сделать скрипт перезапущенным.

    5. Изучите результирующий скрипт развертывания. Непосредственно перед разделом с обозначением «Шаблон скрипта, выполняемого перед развертыванием» должен присутствовать примерно такой код Transact-SQL:

      :setvar CompletedBatches __completedBatches_CompareProjectDB_cd1e348a-8f92-44e0-9a96-d25d65900fca  
      :setvar TotalBatchCount 17  
      GO  
      
      if OBJECT_ID(N'tempdb.dbo.$(CompletedBatches)', N'U') is null  
      begin  
      use tempdb  
      create table [dbo].[$(CompletedBatches)]  
      (  
      BatchId int primary key,  
      Description nvarchar(300)  
      )  
      use [$(DatabaseName)]  
      end  
      
      

      Далее в скрипте развертывания вокруг каждого пакета должны находиться инструкции IF, окружающие эту исходную инструкцию. Например, для инструкции CREATE SCHEMA может появиться следующий скрипт T-SQL:

      IF NOT EXISTS (SELECT 1  
                     FROM   [tempdb].[dbo].[$(CompletedBatches)]  
                     WHERE  [BatchId] = 0)  
          BEGIN  
              EXECUTE sp_executesql @stmt = N'CREATE SCHEMA [Sales]  
          AUTHORIZATION [dbo]';  
              SET NOCOUNT ON;  
              INSERT  [tempdb].[dbo].[$(CompletedBatches)] (BatchId, Description)  
              VALUES                                      (0, N'CreateElementStep Sales batch 0');  
              SET NOCOUNT OFF;  
          END  
      
      

      Следует отметить, что CREATE SCHEMA — это одна из инструкций, которая должна быть заключена в инструкцию EXECUTE sp_executesql в рамках инструкции IF. Такие инструкции, как CREATE TABLE, не требуют инструкции EXECUTE sp_executesql и похожи на следующий пример:

      IF NOT EXISTS (SELECT 1  
                     FROM   [tempdb].[dbo].[$(CompletedBatches)]  
                     WHERE  [BatchId] = 1)  
          BEGIN  
              CREATE TABLE [Sales].[Customer] (  
                  [CustomerID]   INT           IDENTITY (1, 1) NOT NULL,  
                  [CustomerName] NVARCHAR (40) NOT NULL,  
                  [YTDOrders]    INT           NOT NULL,  
                  [YTDSales]     INT           NOT NULL  
              );  
              SET NOCOUNT ON;  
              INSERT  [tempdb].[dbo].[$(CompletedBatches)] (BatchId, Description)  
              VALUES                                      (1, N'CreateElementStep Sales.Customer batch 0');  
              SET NOCOUNT OFF;  
          END  
      
      

      Примечание.

      При развертывании проекта базы данных, которая идентична целевой базе данных, результирующий отчет не является слишком содержательным. Для получения более содержательных результатов разверните изменения в базе данных или новую базу данных.

Развертывание с помощью командной строки, в котором используется созданный файл dacpac

Dacpac-файл является выходным артефактом из сборки проекта SQL. Файл dacpac можно использовать для развертывания схемы из командной строки и включения развертывания с другого компьютера, например компьютера сборки. SqlPackage — это служебная программа командной строки с полным спектром параметров, позволяющих пользователям развертывать dacpac или создавать скрипт развертывания, помимо других действий. Дополнительные сведения см. в описании SqlPackage.exe.

Примечание.

Для успешного развертывания файлов dacpac, созданных из проектов с заданным свойством DeploymentContributors, на машине, которую вы используете, должны быть установлены DLL-библиотеки, включающие ваши участники развертывания. Это связано с тем, что они отмечены как необходимые для успешного завершения развертывания.

Чтобы избавиться от необходимости выполнять это требование, исключите участника развертывания из файла SQLPROJ. Вместо этого укажите, какие участники будут вызываться для выполнения во время развертывания, используя программу SqlPackage с параметром AdditionalDeploymentContributors. Это полезно в тех случаях, когда вы хотите использовать участника только в особых обстоятельствах, таких как развертывание на конкретном сервере.

Следующие шаги

Можно проводить эксперименты с другими типами изменений в планах развертывания до их выполнения. Некоторые типы изменений, которые могут быть внесены, включают следующее:

  • Добавление расширенного свойства ко всем объектам базы данных, с которыми связан номер версии.

  • Добавление или удаление дополнительных инструкций диагностики печати или комментариев из скриптов развертывания.

См. также

Настройка сборки и развертывания базы данных с помощью вкладчиков сборки и развертывания
Walkthrough: Extend Database Project Build to Generate Model Statistics (Пошаговое руководство. Расширение сборки для проекта базы данных для создания статистики модели)
Пошаговое руководство. Расширение процесса развертывания проекта базы данных для анализа плана развертывания