Partilhar via


Passo a passo: Estender a implantação do projeto de banco de dados para modificar o plano de implantação

Você pode criar colaboradores de implantação para executar ações personalizadas ao implantar um projeto SQL. Você pode criar um "DeploymentPlanModifier" ou um "DeploymentPlanExecutor". Use um DeploymentPlanModifier para alterar o plano antes de ser executado e um DeploymentPlanExecutor para executar operações enquanto o plano está sendo executado. Nesta explicação passo a passo, você cria um DeploymentPlanModifier chamado SqlRestartableScriptContributor. O DeploymentPlanModifier SqlRestartableScriptContributor adiciona instruções IF aos lotes no script de implementação para possibilitar que as instruções sejam reexecutadas até serem concluídas, caso ocorra um erro durante a execução do script.

Neste passo a passo, você realiza as seguintes tarefas principais:

Pré-requisitos

Você precisa dos seguintes componentes para concluir este passo a passo:

  • Você deve ter instalado uma versão do Visual Studio que inclua o SQL Server Data Tools e ofereça suporte ao desenvolvimento em C#.

  • Você deve ter um projeto SQL que contém objetos SQL.

  • Uma instância do SQL Server na qual você pode implantar um projeto de banco de dados.

Observação

Este passo a passo destina-se a usuários que já estão familiarizados com os recursos SQL do SQL Server Data Tools. Também é esperado que você esteja familiarizado com conceitos básicos do Visual Studio, como como criar uma biblioteca de classes e como usar o editor de código para adicionar código a uma classe.

Criar um colaborador de implantação

Para criar um colaborador de implantação, você deve executar as seguintes tarefas:

  • Crie um projeto de biblioteca de classes e adicione as referências necessárias.
  • Defina uma classe chamada SqlRestartableScriptContributor que herda de DeploymentPlanModifier.
  • Substituir o método OnExecute.
  • Adicione métodos auxiliares privados.
  • Construa a montagem resultante.

Criar um projeto de biblioteca de classes

  1. Crie um projeto de biblioteca de classes C# (.NET Framework) chamado MyOtherDeploymentContributor.
  2. Renomeie o arquivo "Class1.cs" para "SqlRestartableScriptContributor.cs".
  3. No Gerenciador de Soluções, clique com o botão direito do mouse no nó do projeto e selecione Adicionar Referência.
  4. Selecione System.ComponentModel.Composition na guia Frameworks.
  5. No menu Project, selecione a opção Gerir pacotes NuGet. Instale as versões estáveis mais recentes para Microsoft.SqlServer.DacFx.

Em seguida, comece a adicionar código à classe.

Definir a classe SqlRestartableScriptContributor

  1. No editor de códigos, atualize o arquivo class1.cs para corresponder aos seguintes usando instruções:

    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. Atualize a definição de classe para corresponder ao exemplo a seguir:

        /// <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
    {
    }
    

    Agora você definiu o seu colaborador de implementação que herda de DeploymentPlanModifier. Durante os processos de compilação e implementação, os contribuidores personalizados são carregados de um diretório padrão de extensões. Os contribuidores que modificam o plano de implantação são identificados com um atributo ExportDeploymentPlanModifier. Esse atributo é necessário para que os colaboradores possam ser descobertos. Este atributo deve ser semelhante ao seguinte decorador de funções:

    [ExportDeploymentPlanModifier("MyOtherDeploymentContributor.RestartableScriptContributor", "1.0.0.0")]
    
  3. Adicione as seguintes declarações de membro:

         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
    ";
    

    Em seguida, substitua o método OnExecute para adicionar o código que deseja executar quando um projeto de banco de dados é implantado.

Substituir OnExecute

  1. Adicione o seguinte método à sua classe 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
    }
    

    Você substitui o método OnExecute da classe base, DeploymentPlanContributor. DeploymentPlanContributor é a classe base tanto para DeploymentPlanModifier quanto para DeploymentPlanExecutor. Um objeto DeploymentPlanContributorContext é passado para o método OnExecute, fornecendo acesso a quaisquer argumentos especificados, ao modelo de origem e banco de dados do destino, ao plano de implantação e às opções de implantação. Neste exemplo, obtemos o plano de implantação e o nome do banco de dados de destino.

  2. Agora, adicione o início de um corpo ao método 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);
    

    Neste código, definimos algumas variáveis locais e configuramos o loop que lida com o processamento de todas as etapas do plano de implantação. Depois que o loop for concluído, teremos que fazer algum pós-processamento e, em seguida, descartar a tabela temporária que criamos durante a implantação para acompanhar o progresso conforme o plano é executado. Os principais tipos aqui são: DeploymentStep e DeploymentScriptStep. Um método chave é AddAfter.

  3. Agora adicione o processamento de etapa adicional, para substituir o comentário que diz "Adicionar processamento de etapa adicional aqui":

    // Look for steps that mark the pre/post deployment scripts
    // These steps are always 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 are enclosed in an if statement that checks
        // 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
    }
    

    Os comentários de código explicam o processamento. De forma geral, este código procura as etapas relevantes, ignorando as restantes e interrompendo quando se atinge o início das etapas após a implantação. Se a etapa contiver instruções que devemos cercar com condicionais, executaremos processamento extra. Os principais tipos, métodos e propriedades incluem os seguintes componentes da biblioteca DacFx: BeginPreDeploymentScriptStep, BeginPostDeploymentScriptStep, TSqlObject, TSqlScript, Script DeploymentScriptDomStepe SqlPrintStep.

  4. Agora, adicione o código de processamento em lote substituindo o comentário que diz "Adicionar processamento em lote aqui":

        // Create the if statement that contains 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++;
    

    Esse código cria uma IF instrução junto com um BEGIN/END bloco. Em seguida, realizamos um processamento extra nas declarações do lote. Quando isso estiver concluído, adicionamos uma INSERT instrução para adicionar informações à tabela temporária que acompanha o progresso da execução do script. Por fim, atualize o lote, substituindo as instruções que costumavam estar lá pela nova IF que contém essas instruções dentro dele. Os principais tipos, métodos e propriedades incluem: IfStatement, BeginEndBlockStatement, StatementList, TSqlBatch, PredicateSetStatement, SetOptions e InsertStatement.

  5. Agora, adicione o corpo do loop de processamento de declarações. Substitua o comentário que indica "Adicionar processamento de uma declaração adicional aqui":

    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);
    

    Para cada instrução no lote, se a instrução for de um tipo que deve ser encapsulada numa instrução sp_executesql, modifique a instrução de acordo. Em seguida, o código adiciona a instrução à lista de instruções para o BEGIN/END bloco que você criou. Os principais tipos, métodos e propriedades incluem TSqlStatement e ExecuteStatement.

  6. Finalmente, adicione a seção de pós-processamento no lugar do comentário que diz "Adicionar pós-processamento adicional aqui":

    // Declare a SqlCmd variables.
    //
    // CompletedBatches variable - defines the name of the table in tempdb that tracks 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 use to track the work that we are doing
    DeploymentScriptStep createStatusTableStep = new DeploymentScriptStep(CreateCompletedBatchesTable);
    base.AddBefore(context.PlanHandle, beforePreDeploy, createStatusTableStep);
    

    Se nosso processamento encontrou uma ou mais etapas que cercamos com uma instrução condicional, devemos adicionar instruções ao script de implantação para definir variáveis SQLCMD. As variáveis são:

    • CompletedBatches Contém um nome exclusivo para a tabela temporária que o script de implantação usa para controlar quais lotes são concluídos com êxito quando o script é executado

    • TotalBatchCount Contém o número total de lotes no script de implantação

    Outros tipos, propriedades e métodos de interesse incluem:

    StringBuilder, DeploymentScriptStep e AddBefore.

    Em seguida, você define os métodos auxiliares chamados por esse método.

Adicionar os métodos auxiliares

  • Vários métodos auxiliares devem ser definidos. Os métodos importantes incluem:

    Método Description
    CreateExecuteSQL Defina o método CreateExecuteSQL para envolver uma instrução fornecida com uma instrução EXECsp_executesql. Os principais tipos, métodos e propriedades incluem os seguintes componentes da API DacFx: ExecuteStatement, ExecutableProcedureReference, SchemaObjectName, ProcedureReferencee ExecuteParameter.
    CreateCompletedBatchesName Defina o método CreateCompletedBatchesName. Esse método cria o nome que é inserido na tabela temporária para um lote. Os principais tipos, métodos e propriedades incluem os seguintes componentes da API DacFx: SchemaObjectName.
    IsStatementEscaped Defina o método IsStatementEscaped. Esse método determina se o tipo de elemento de modelo requer que a instrução seja encapsulada em uma EXECsp_executesql instrução antes de poder ser incluída em uma IF instrução. Os principais tipos, métodos e propriedades incluem os seguintes componentes da API DacFx: TSqlObject.ObjectType, ModelTypeClass e a propriedade TypeClass para os seguintes tipos de modelo: Schema, Procedure, View, TableValuedFunction, ScalarFunction, DatabaseDdlTrigger, DmlTrigger, ServerDdlTrigger.
    CreateBatchCompleteInsert Defina o método CreateBatchCompleteInsert. Esse método cria a INSERT instrução que é adicionada ao script de implantação para controlar o progresso da execução do script. Os principais tipos, métodos e propriedades incluem os seguintes componentes da API DacFx: InsertStatement, NamedTableReference, ColumnReferenceExpression, ValuesInsertSource e RowValue.
    CreateIfNotExecutedStatement Defina o método CreateIfNotExecutedStatement. Esse método gera uma IF instrução que verifica se a tabela de execução de lote temporário indica que esse lote já foi executado. Os principais tipos, métodos e propriedades incluem: IfStatement, ExistsPredicate, ScalarSubquery, NamedTableReference, WhereClause, ColumnReferenceExpression, IntegerLiteral, BooleanComparisonExpression e BooleanNotExpression.
    GetStepInfo Defina o método GetStepInfo. Esse método extrai informações sobre o elemento de modelo usado para criar o script da etapa, além do nome da etapa. Os tipos e métodos de interesse incluem: DeploymentPlanContributorContext, DeploymentScriptDomStep, TSqlObject, CreateElementStep, AlterElementStep e DropElementStep.
    GetElementName Cria um nome formatado para um TSqlObject.
  1. Adicione o seguinte código para definir os métodos auxiliares:

    /// <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 is 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 looks 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. Salve as alterações em SqlRestartableScriptContributor.cs.

Em seguida, você cria a biblioteca de classes.

Assine e construa a montagem

  1. No menu Project, selecione Propriedades do MyOtherDeploymentContributor.

  2. Selecione a guia Assinatura .

  3. Selecione Assinar a montagem.

  4. Em Escolha um arquivo de chave de nome forte, selecione <Novo>.

  5. Na caixa de diálogo Criar Chave de Nome Forte, em Nome do ficheiro de chave, digite MyRefKey.

  6. (facultativo) Você pode especificar uma senha para seu arquivo de chave de nome forte.

  7. Selecione OK.

  8. No menu Arquivo , selecione Salvar tudo.

  9. No menu Compilação, selecione Compilar Solução.

    A seguir, deve-se instalar o assembly para que seja carregado ao implantar projetos SQL.

Instalar um colaborador de implantação

Para instalar um colaborador de implantação, você deve copiar o assembly e o arquivo associado .pdb para a pasta Extensões.

Instalar o assembly MyOtherDeploymentContributor

  1. Em seguida, copie as informações do assembly para o diretório Extensões. Quando o Visual Studio 2022 é iniciado, ele identifica todas as extensões no diretório e nos %ProgramFiles%\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\Extensions\Microsoft\SQLDB\DAC subdiretórios e as disponibiliza para uso.

  2. Copie o arquivo de assembly MyOtherDeploymentContributor.dll do diretório de saída para o %ProgramFiles%\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\Extensions\Microsoft\SQLDB\DAC diretório. Por padrão, o caminho do arquivo compilado .dll é YourSolutionPath\YourProjectPath\bin\Debug ou YourSolutionPath\YourProjectPath\bin\Release.

Executar ou testar seu colaborador de implantação

Para executar ou testar seu colaborador de implantação, você deve executar as seguintes tarefas:

  • Adicione propriedades ao .sqlproj arquivo que você planeja criar.

  • Implante o projeto de banco de dados usando o MSBuild e fornecendo os parâmetros apropriados.

Adicionar propriedades ao arquivo de projeto SQL (.sqlproj)

Você sempre deve atualizar o arquivo de projeto SQL para especificar a ID dos colaboradores que deseja executar. Você pode atualizar o projeto SQL de duas maneiras:

  1. Você pode modificar manualmente o .sqlproj arquivo para adicionar os argumentos necessários. Você pode optar por fazer isso se seu colaborador não tiver nenhum argumento de colaborador necessário para a configuração ou se você não pretende reutilizar o colaborador de compilação em um grande número de projetos. Se você escolher essa opção, adicione as seguintes instruções ao .sqlproj arquivo após o primeiro nó Importar no arquivo:

    <PropertyGroup>
      <DeploymentContributors>
        $(DeploymentContributors); MyOtherDeploymentContributor.RestartableScriptContributor
      </DeploymentContributors>
    </PropertyGroup>
    
  2. O segundo método é criar um arquivo de destinos contendo os argumentos de contribuidor necessários. Isso é útil se você estiver usando o mesmo colaborador para vários projetos e tiver argumentos de contribuidor necessários, pois inclui os valores padrão. Nesse caso, crie um arquivo de destino no caminho de extensões do MSBuild:

    1. Navegue até %ProgramFiles%\MSBuild.

    2. Crie uma nova pasta "MyContributors" onde seus arquivos de destino são armazenados.

    3. Crie um novo arquivo "MyContributors.targets" dentro deste diretório, adicione o seguinte texto a ele e salve o arquivo:

      <?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. Dentro do .sqlproj ficheiro para qualquer projeto que pretende configurar contribuintes, importe o ficheiro de alvos adicionando a seguinte instrução ao .sqlproj ficheiro após o <nó Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\SSDT\Microsoft.Data.Tools.Schema.SqlTasks.targets" /> no ficheiro:

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

Depois de seguir uma dessas abordagens, você pode usar o MSBuild para passar os parâmetros para compilações de linha de comando.

Observação

Você sempre deve atualizar a propriedade "DeploymentContributors" para especificar sua ID de colaborador. Este é o mesmo ID usado no atributo "ExportDeploymentPlanModifier" no ficheiro de origem do colaborador. Sem isso, seu colaborador não é executado ao criar o projeto. A propriedade "ContributorArguments" precisa ser atualizada somente se você tiver argumentos necessários para que seu colaborador seja executado.

Implantar o projeto de banco de dados

  • Seu projeto pode ser publicado ou implantado normalmente dentro do Visual Studio. Abra uma solução que contenha o seu projeto SQL e escolha a opção Publicar... no menu de contexto para o projeto ou use F5 para uma implementação de depuração no LocalDB. Neste exemplo, usamos o "Publicar..." para gerar um script de implantação.

    1. Abra o Visual Studio e abra a solução que contém seu projeto SQL.

    2. Clique com o botão direito do mouse no projeto no Gerenciador de Soluções e escolha a opção Publicar....

    3. Defina o nome do servidor e o nome do banco de dados para publicar.

    4. Escolha Gerar Script nas opções na parte inferior da caixa de diálogo. Esta ação cria um script que pode ser usado para implantação. Podemos examinar esse script para verificar se nossas IF instruções foram adicionadas para torná-lo reinicializável.

    5. Examine o script de implantação resultante. Pouco antes da seção rotulada "Pre-Deployment Script Template", você verá algo semelhante à seguinte sintaxe 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
      

      Mais adiante no script de implantação, em torno de cada lote, você verá uma IF instrução que envolve a instrução original. Por exemplo, o seguinte script T-SQL pode aparecer para uma CREATE SCHEMA instrução:

      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 é uma das declarações que devem ser incluídas dentro de uma EXECUTEsp_executesql declaração dentro da IF declaração. Instruções como CREATE TABLE não exigem a EXECUTEsp_executesql instrução e se assemelham ao exemplo a seguir:

      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
      

      Observação

      Se você implantar um projeto de banco de dados idêntico ao banco de dados de destino, o relatório resultante não será muito significativo. Para obter resultados mais significativos, implante alterações em um banco de dados ou implante um novo banco de dados.

Implantação de linha de comando usando o arquivo dacpac gerado

O artefato de saída de uma compilação de projeto SQL é um .dacpac arquivo. Um .dacpac arquivo pode ser usado para implantar o esquema a partir da linha de comando e que pode habilitar a implantação de uma máquina diferente, como uma máquina de compilação. SqlPackage é um utilitário de linha de comando com uma gama completa de opções que permite aos usuários implantar um dacpac ou gerar um script de implantação, entre outras ações. Para obter mais informações, consulte SqlPackage.

Observação

Para implantar com sucesso dacpacs criados a partir de projetos com a propriedade DeploymentContributors definida, as DLLs que contêm os colaboradores de implementação devem ser instaladas na máquina em uso. Isso ocorre porque eles foram marcados como necessários para que a implantação seja concluída com êxito.

Para evitar esse requisito, exclua o colaborador de implantação do .sqlproj arquivo. Em vez disso, especifique os colaboradores a serem executados durante a implantação usando SqlPackage com o parâmetro AdditionalDeploymentContributors. Isso é útil nos casos em que você deseja usar apenas um colaborador para circunstâncias especiais, como implantar em um servidor específico.