次の方法で共有


チュートリアル: データベース プロジェクトの配置を拡張して配置計画を変更する

配置コントリビューターを作成して、SQL プロジェクトの配置時にカスタム アクションを実行できます。 DeploymentPlanModifier または DeploymentPlanExecutor を作成できます。 DeploymentPlanModifier を使用してプランを実行する前に変更し、プランの実行中に操作を実行する DeploymentPlanExecutor を使用します。 このチュートリアルでは、SqlRestartableScriptContributor という名前の DeploymentPlanModifier を作成します。 DeploymentPlanModifier SqlRestartableScriptContributor は、実行中にエラーが発生した場合に完了するまでスクリプトを再実行できるように、デプロイ スクリプト内のバッチに IF ステートメントを追加します。

このチュートリアルでは、次の主なタスクを達成します。

必須コンポーネント

このチュートリアルを実行するには、次のコンポーネントが必要です。

  • C# の開発をサポートする、SQL Server Data Tools を含む Visual Studio のバージョンがインストールされていること。

  • SQL オブジェクトを含む SQL プロジェクト。

  • データベース プロジェクトを配置できる SQL Server のインスタンス。

Note

このチュートリアルは、既に SQL Server Data Tools の SQL 機能を使い慣れているユーザーを対象としています。 また、クラス ライブラリの作成方法や、コード エディターを使用してクラスにコードを追加する方法など、Visual Studio の基本的な概念についても理解している必要があります。

デプロイ貢献者を登録する

配置コントリビューターを作成するには、次のタスクを実行する必要があります。

  • クラス ライブラリ プロジェクトを作成し、必要な参照を追加する。
  • DeploymentPlanModifier から継承する SqlRestartableScriptContributor という名前のクラスを定義する。
  • OnExecute メソッドをオーバーライドする。
  • プライベート ヘルパー メソッドを追加する。
  • 結果として得られるアセンブリをビルドする。

クラス ライブラリ プロジェクトを作成する

  1. MyOtherDeploymentContributor という名前の C# クラス ライブラリ (.NET Framework) を作成します。
  2. "Class1.cs" ファイルの名前を "SqlRestartableScriptContributor.cs" に変更します。
  3. ソリューション エクスプローラーで、プロジェクト ノードを右クリックし、[参照の 追加] を選択します。
  4. [フレームワーク] タブで System.ComponentModel.Composition を選択します。
  5. [プロジェクト] メニューから、 [NuGet パッケージの管理] オプションを選択します。 Microsoft.SqlServer.DacFx の最新の安定版リリースをインストールします。

次に、コードをクラスに追加します。

SqlRestartableScriptContributor クラスを定義する

  1. コード エディターで、次の using ステートメントに一致するように、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
    }
    

    基本クラス DeploymentPlanContributor から OnExecute メソッドをオーバーライドします。 DeploymentPlanContributor は、DeploymentPlanModifierDeploymentPlanExecutor の両方の基本クラスです。 OnExecute メソッドに、DeploymentPlanContributorContext オブジェクトが渡され、指定されたすべての引数、ソースとターゲットのデータベース モデル、配置プラン、および配置オプションにアクセスできるようになります。 この例では、配置計画とターゲット データベース名を取得します。

  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. 次に、"Add additional step processing here" というコメントを次のコードで置き換えて、ステップ処理を追加します。

    // 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 API コンポーネントである BeginPreDeploymentScriptStepBeginPostDeploymentScriptStepTSqlObjectTSqlScript、Script、DeploymentScriptDomStep、および SqlPrintStep です。

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

    このコードは、IFBEGIN/ ブロックと共にENDステートメントを作成します。 その後、バッチ内のステートメントに対して追加の処理を実行します。 これが完了したら、 INSERT ステートメントを追加して、スクリプトの実行の進行状況を追跡する情報を一時テーブルに追加します。 最後に、バッチを更新し、以前そこにあったステートメントを新しいIFで置き換え、その中にそれらのステートメントを含めます。 キーの種類、メソッド、プロパティには、 IfStatementBeginEndBlockStatementStatementListTSqlBatchPredicateSetStatementSetOptionsInsertStatement があります。

  5. ステートメント処理ループの本体を追加します。 "Add additional statement processing here" というコメントを次のコードで置き換えます。

    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. 最後に、"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);
    

    条件付きステートメントで囲んだステップが 1 つ以上見つかった場合は、配置スクリプトにステートメントを追加して SQLCMD 変数を定義する必要があります。 変数は次のとおりです。

    • CompletedBatches には、デプロイ スクリプトがスクリプトの実行時に正常に完了したバッチを追跡するために使用する一時テーブルの一意の名前が含まれています

    • TotalBatchCount には、デプロイ スクリプト内のバッチの合計数が含まれます

    その他の重要な型、プロパティ、およびメソッドは次のとおりです。

    StringBuilderDeploymentScriptStep、および AddBefore

    次に、このメソッドによって呼び出されるヘルパー メソッドを定義します。

ヘルパー メソッドを追加する

  • いくつかのヘルパー メソッドを定義する必要があります。 重要なメソッドを次に示します。

    メソッド Description
    CreateExecuteSQL 指定されたステートメントを EXECsp_executesql ステートメントで囲む CreateExecuteSQL メソッドを定義します。 重要な型、メソッド、およびプロパティは、次の DacFx API コンポーネントである ExecuteStatementExecutableProcedureReferenceSchemaObjectNameProcedureReference、および ExecuteParameter です。
    CreateCompletedBatchesName CreateCompletedBatchesName メソッドを定義します。 このメソッドは、バッチの一時テーブルに挿入される名前を作成します。 キーの型、メソッド、およびプロパティには、次の DacFx API コンポーネントが含まれます: SchemaObjectName
    IsStatementEscaped IsStatementEscaped メソッドを定義します。 このメソッドは、model 要素の型をEXECステートメント内で囲む前に、ステートメントを sp_executesqlIF ステートメントでラップする必要があるかどうかを判断します。 重要な型、メソッド、およびプロパティは、次の 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. 次のコードを追加して、ヘルパー メソッドを定義します。

    /// <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. [キー ファイルの厳密な名前を選択] で、[New] を選択します。

  5. [厳密な名前キーの作成] ダイアログ ボックスで、 [キー ファイル] に「 MyRefKey」と入力します。

  6. (省略可能) 厳密な名前のキー ファイルにパスワードを指定できます。

  7. [OK] を選択.

  8. [ファイル] メニューの [すべてを保存] をクリックします。

  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\Debug または YourSolutionPath\YourProjectPath\bin\Release

デプロイメントの貢献者を実行またはテストする

配置コントリビューターを実行またはテストするには、次のタスクを実行する必要があります。

  • ビルドする .sqlproj ファイルにプロパティを追加します。

  • MSBuild を使用し、適切なパラメーターを指定して、データベース プロジェクトを配置する。

SQL プロジェクト (.sqlproj) ファイルにプロパティを追加する

実行するコントリビューターの ID を指定するには、必ず SQL プロジェクト ファイルを更新する必要があります。 SQL プロジェクトは、次の 2 つの方法のいずれかで更新できます。

  1. .sqlproj ファイルを手動で変更して、必要な引数を追加できます。 構成に必要な共同作成者引数がない場合、または多数のプロジェクトでビルド共同作成者を再利用しない場合は、これを行うことを選択できます。 このオプションを選択した場合は、ファイル内の最初の Import ノードの後に、次のステートメントを .sqlproj ファイルに追加します。

    <PropertyGroup>
      <DeploymentContributors>
        $(DeploymentContributors); MyOtherDeploymentContributor.RestartableScriptContributor
      </DeploymentContributors>
    </PropertyGroup>
    
  2. もう 1 つの方法では、必要なコントリビューター引数を含むターゲット ファイルを作成します。 これは、複数のプロジェクトで同じ共同作成者を使用していて、既定値が含まれるため、共同作成者の引数が必要な場合に便利です。 この場合は、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 ファイル内で、.sqlprojImport Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\SSDT\Microsoft.Data.Tools.Schema.SqlTasks.targets" /< ノードの後に次の >ステートメントを追加して、ターゲット ファイルをインポートします。

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

上記の方法のいずれかを実行すると、MSBuild を使用して、コマンド ライン ビルドのパラメーターを渡すことができます。

Note

コントリビューター ID を指定するには、必ず "DeploymentContributors" プロパティを更新する必要があります。 この ID は、コントリビューター ソース ファイル内の "ExportDeploymentPlanModifier" 属性で使用されているのと同じ ID です。 このコンポーネントがないと、プロジェクトのビルド時に実行されません。 "ContributorArguments" プロパティは、コントリビューターの実行に必要な引数がある場合にのみ更新する必要があります。

データベース プロジェクトを配置する

  • プロジェクトは、通常どおり Visual Studio 内で発行または配置できます。 SQL プロジェクトを含むソリューションを開き、[発行] を選択します。プロジェクトの右クリック コンテキスト メニューからオプションを選択するか、LocalDB へのデバッグ配置に F5 キーを使用します。 この例では、[発行...] ダイアログを使用して配置スクリプトを生成します。

    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_executesqlIF ステートメント内で囲む必要があるステートメントの 1 つです。 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
      

      Note

      ターゲット データベースと同じデータベース プロジェクトを配置する場合、結果のレポートはあまり意味がありません。 意味のある結果を得るには、データベースに対する変更を配置するか、新しいデータベースを配置します。

生成された dacpac ファイルを使用したコマンド ライン配置

SQL プロジェクト ビルドからの出力成果物は、 .dacpac ファイルです。 .dacpac ファイルを使用して、コマンド ラインからスキーマをデプロイできます。これにより、ビルド マシンなどの別のコンピューターからのデプロイを有効にすることができます。 SqlPackage は、さまざまなオプションを使用してコマンド ライン ユーティリティです。これらのオプションにより、ユーザーは、特に dacpac の配置や配置スクリプトの生成を実行できます。 詳細については、「 SqlPackage」を参照してください。

Note

DeploymentContributors プロパティが定義されたプロジェクトからビルドされた dacpac を正常に配置するには、展開コントリビューターを含む DLL を使用しているマシンにインストールする必要があります。 これは、配置を正常に完了するためにこれらの DLL が必須とマークされているためです。

この要件を回避するには、 .sqlproj ファイルから展開共同作成者を除外します。 代わりに、AdditionalDeploymentContributors パラメーターを指定した SqlPackage を使用して、配置中にコントリビューターを実行するように指定します。 これは、特殊な状況 (特定のサーバーへの配置など) でコントリビューターを使用する場合にのみ役立ちます。