共用方式為


逐步解說:擴充資料庫專案部署以修改部署計劃

您可以建立部署參與者,以便在部署 SQL 專案時執行自訂動作。 您可以建立 DeploymentPlanModifierDeploymentPlanExecutor。 使用 DeploymentPlanModifier 在執行計劃之前變更計劃,並使用 DeploymentPlanExecutor 在執行計劃時執行作業。 在此逐步解說中,您會建立一個 DeploymentPlanModifier,並將其命名為 SqlRestartableScriptContributor。 DeploymentPlanModifier SqlRestartableScriptContributor 會將陳述式新增至 IF 部署腳本中的批次,以讓腳本能夠重新執行,直到在執行期間發生錯誤時完成為止。

在此逐步解說中,您將完成以下主要任務:

必要條件

您需要下列元件才能完成這個逐步解說:

  • 您必須已安裝包含 SQL Server Data Tools 且支援 C# 或 VB 開發的 Visual Studio 版本。

  • 您必須擁有包含 SQL 物件的 SQL 專案。

  • 可以部署資料庫專案的 SQL Server 執行個體。

注意

這個逐步解說適用於已經熟悉 SQL Server Data Tools SQL 功能的使用者。 您也應該熟悉基本的 Visual Studio 概念,例如如何建立類別程式庫,以及如何使用程式碼編輯器將程式碼新增至類別。

建立部署參與者

若要建立部署貢獻者,您必須執行下列工作:

  • 建立類別庫專案並加入必要參考。
  • 定義繼承自 DeploymentPlanModifier 且名稱為 SqlRestartableScriptContributor 的類別。
  • 覆寫 OnExecute 方法。
  • 加入私人輔助方法。
  • 建置產生的組件。

建立類別庫專案

  1. 建立名稱為 MyOtherDeploymentContributor 的 C# 類別庫專案。
  2. 將 "Class1.cs" 檔案重新命名為 "SqlRestartableScriptContributor.cs"。
  3. 在 [方案總管] 中,以滑鼠右鍵按一下專案節點,然後選取 [新增參考]。
  4. 選取 [框架] 索引標籤上的 [System.ComponentModel.Composition]。
  5. 專案 功能表選取管理 NuGet 套件選項。 安裝Microsoft.SqlServer.DacFx的最新穩定版本。

下一步,開始將程式碼加入至類別。

定義 SqlRestartableScriptContributor 類別

  1. 在程式碼編輯器中,更新 class1.cs 檔案,以符合下列 using陳述式:

    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
    }
    

    您可以從基底類別DeploymentPlanContributor覆寫OnExecute方法。 DeploymentPlanContributorDeploymentPlanModifierDeploymentPlanExecutor的基底類別。 將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);
    

    在這個程式碼,我們定義一些區域變數,而且設定會處理部署計畫中所有步驟的迴圈。 迴圈完成後,我們必須進行一些後處理,然後刪除我們在部署期間建立的暫存表,以追蹤計劃執行時的進度。 此處的重要類型為:DeploymentStepDeploymentScriptStep。 重要的方法是 AddAfter。

  3. 現在加入額外的步驟處理,並替換掉「在此加入額外的步驟處理」的註解:

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

    程式碼註解說明處理程序。 在高層次上,此程式碼尋找您重視的步驟,略過其他步驟,並在到達部署後步驟開頭時停止。 如果該步驟包含我們必須用條件括住的語句,我們會執行額外的處理。 重要型別、方法和屬性包含 DacFx 媒體櫃中的以下元件:BeginPreDeploymentScriptStepBeginPostDeploymentScriptStepTSqlObjectTSqlScript、Script、DeploymentScriptDomStepSqlPrintStep

  4. 現在,將批次處理程式碼加入,替換掉註解為 "Add batch processing here" 的部分:

        // 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++;
    

    此程式碼會建立陳述IF式以及區塊BEGIN/END。 然後,我們對批次中的陳述式執行額外的處理。 完成後,我們新增一個 INSERT 語句,將資訊新增至追蹤腳本執行進度的暫存表。 最後,將批次更新為將先前存在的陳述式替換為包含這些陳述式的新 IF。 索引鍵類型、方法和屬性包括: IfStatementBeginEndBlockStatementStatementListTSqlBatchPredicateSetStatementSetOptionsInsertStatement

  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 區塊的陳述式清單。 重要型別、方法和屬性包含 TSqlStatementExecuteStatement

  6. 最後,加入後置處理區段,以取代註解 "Add additional post-processing here":

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

    如果處理程序找到以條件陳述式圍繞的一個或多個步驟,我們必須將陳述式加入至部署指令碼以定義 SQLCMD 變數。 變數為:

    • CompletedBatches 包含暫存資料表的唯一名稱,部署指令碼用來追蹤指令碼執行時成功完成的批次

    • TotalBatchCount 包含部署指令碼中的批次總數

    其他相關型別、屬性和方法包含:

    StringBuilderDeploymentScriptStepAddBefore

    下一步,您會定義這個方法呼叫的 Helper 方法。

新增協助程式方法

  • 必須定義數個協助程式方法。 重要方法包含:

    方法 Description
    CreateExecuteSQL 定義 CreateExecuteSQL 方法,以提供的陳述式作為基礎,將它以EXECsp_executesql陳述式包覆起來。 重要型別、方法和屬性包含以下 DacFx API 元件:ExecuteStatementExecutableProcedureReferenceSchemaObjectNameProcedureReferenceExecuteParameter
    CreateCompletedBatchesName 定義 CreateCompletedBatchesName 方法。 這個方法會建立插入批次暫存資料表的名稱。 鍵類型、方法和屬性包括以下 DacFx API 元件:SchemaObjectName
    IsStatementEscaped 定義 IsStatementEscaped 方法。 這個方法會決定模型元素的類型是否需要將陳述式包裝在陳述式中 EXECsp_executesql ,然後才能將其括在陳述式中 IF 。 重要類型、方法和屬性包含以下 DacFx API 元件:TSqlObject.ObjectType、ModelTypeClass 和 TypeClass 屬性,適用於下列模型類型:Schema、Procedure、View、TableValuedFunction、ScalarFunction、DatabaseDdlTrigger、DmlTrigger、ServerDdlTrigger。
    CreateBatchCompleteInsert 定義 CreateBatchCompleteInsert 方法。 這個方法會建立 INSERT 新增至部署指令碼的陳述式,以追蹤指令碼執行的進度。 金鑰類型、方法和屬性包含以下 DacFx API 元件:InsertStatement、NamedTableReference、ColumnReferenceExpression、ValuesInsertSource 和 RowValue。
    CreateIfNotExecutedStatement 定義 CreateIfNotExecutedStatement 方法。 這個方法會產生一個 IF 陳述式,檢查暫存批次執行表是否指出此批次已執行。 索引鍵類型、方法和屬性包括:IfStatement、ExistsPredicate、ScalarSubquery、NamedTableReference、WhereClause、ColumnReferenceExpression、IntegerLiteral、BooleanComparisonExpression 和 BooleanNotExpression。
    GetStepInfo 定義 GetStepInfo 方法。 除了步驟名稱之外,這個方法還會擷取用來建立步驟指令碼的模型項目資訊。 感興趣的類型和方法包括: DeploymentPlanContributorContextDeploymentScriptDomStepTSqlObjectCreateElementStepAlterElementStepDropElementStep
    GetElementName 建立 TSqlObject 的格式化名稱。
  1. 加入下列程式碼以定義 Helper 方法:

    /// <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. 將變更儲存至 SqlRestartableScriptContributor.cs。

下一步,您會建置類別庫。

簽署並建置組件

  1. [專案]功能表上,選取[MyOtherDeploymentContributor 屬性]

  2. 選取 [ 簽署 ] 索引標籤。

  3. 選取 [簽署組件]。

  4. [選擇強式名稱金鑰檔案] 中,選取 [新增<]。>

  5. 在 [ 建立強式名稱金鑰 ] 對話方塊中的 [ 金鑰檔名稱],輸入 MyRefKey

  6. (選擇性) 您可以為強式名稱金鑰檔指定密碼。

  7. 請選擇 [確定]

  8. 在 [File] \(檔案\) 功能表上,選取 [Save All] \(全部儲存\)

  9. 在 [建置] 功能表上,選取 [建置方案]

    接下來,您必須安裝該元件,以便在部署 SQL 專案時能夠載入。

安裝部署參與者

若要安裝部署參與者,您必須將元件和相關聯 .pdb 的檔案複製到 Extensions 資料夾。

安裝 MyOtherDeploymentContributor 元件

  1. 接下來,您將組合資訊複製到 Extensions 目錄。 當 Visual Studio 2022 啟動時,它會識別目錄和子目錄中 %ProgramFiles%\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\Extensions\Microsoft\SQLDB\DAC 的任何延伸模組,並讓它們可供使用。

  2. MyOtherDeploymentContributor.dll 組件檔案從輸出目錄複製到目錄 %ProgramFiles%\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\Extensions\Microsoft\SQLDB\DAC 。 依預設,編譯檔案 .dll 的路徑是 YourSolutionPath\YourProjectPath\bin\DebugYourSolutionPath\YourProjectPath\bin\Release

執行或測試您的部署參與者

若要執行或測試部署工具,您必須執行下列工作:

  • 將屬性新增至您計劃建置的 .sqlproj 檔案。

  • 使用 MSBuild 並提供適當的參數,部署資料庫專案。

將屬性新增至 SQL 專案 (.sqlproj) 檔案

您一定要更新 SQL 專案檔,指定要執行之參與者的識別碼。 您可以透過下列兩種方式之一來更新 SQL 專案:

  1. 您可以手動修改 .sqlproj 檔案以新增所需的引數。 如果您的參與者沒有設定所需的任何參與者引數,或者您不打算在大量專案中重複使用組建參與者,您可以選擇執行此動作。 如果您選擇此選項,請在檔案中第一個 [匯入] 節點之後將下列陳述式 .sqlproj 新增至檔案:

    <PropertyGroup>
      <DeploymentContributors>
        $(DeploymentContributors); MyOtherDeploymentContributor.RestartableScriptContributor
      </DeploymentContributors>
    </PropertyGroup>
    
  2. 第二個方法是建立包含必要參與者引數的目標檔案。 如果您對多個專案使用相同的貢獻者,並且需要貢獻者引數,這很有用,因為它包含預設值。 在此情況下,請在 MSBuild 擴充路徑建立目標檔案:

    1. 導航至 %ProgramFiles%\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,匯入目標檔案,方法是將下列陳述式.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" 屬性。

部署資料庫專案

  • 您的專案可以在 Visual Studio 中正常發行或部署。 開啟包含 SQL 專案的解決方案,然後從專案的右鍵選單中選擇「發行...」選項,或使用 F5 將偵錯部署到 LocalDB。 在這個範例中,會使用 [發行...] 對話方塊產生部署指令碼。

    1. 開啟 Visual Studio,並開啟包含 SQL 專案的方案。

    2. 在 [方案總管] 中,以滑鼠右鍵按一下專案,然後選擇 [發行...] 選項。

    3. 設定發行目標伺服器名稱和資料庫名稱。

    4. 從對話方塊的底部選項中選擇 [產生指令碼]。 這會建立可用於部署的指令碼。 我們可以檢查此指令碼,以驗證是否已新增 IF 語句,使指令碼能夠重新啟動。

    5. 檢查產生的部署指令碼。 在標示 "Pre-Deployment Script Template" 的區段之前,您應該會看到類似下列 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 陳述式。 例如,下列 T-SQL 腳本可能會針對 CREATE SCHEMA 陳述式 出現:

      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是必須包含在EXECUTEsp_executesql陳述式內的IF陳述式中的陳述式之一。 像CREATE TABLE這樣的語句不需要EXECUTEsp_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 檔案進行命令列部署

SQL 專案建置的輸出構件是檔案 .dacpac.dacpac檔案可用來從命令列部署架構,並且可以從不同的機器(例如建置機器)進行部署。 SqlPackage 是一個命令列公用程式,具有各種選項,讓使用者能夠部署 dacpac、產生部署指令碼,以及執行其他動作。 如需詳細資訊,請參閱 SqlPackage

注意

若要成功部署從專案中包含已定義 DeploymentContributors 屬性所建置的 dacpac,必須在所使用的計算機上安裝包含這些部署貢獻者的 DLL。 這是因為它們已經標示為部署順利完成的必要項。

若要避免此需求,請從 .sqlproj 檔案中排除部署參與者。 或者,使用 SqlPackage 與 AdditionalDeploymentContributors 參數來指定要在部署期間執行的參與者。 在您只想在特殊情況下使用協作者,例如部署到特定伺服器的時候,這非常有用。