연습: 데이터베이스 프로젝트 배포를 확장하여 배포 계획 수정

데이터베이스 프로젝트를 배포할 때 사용자 지정 작업을 수행하는 배포 참가자를 만들 수 있습니다. DeploymentPlanModifier 또는 DeploymentPlanExecutor 중 하나를 만들 수 있습니다. 계획을 실행하기 전에 변경하려면 DeploymentPlanModifier를 사용하고, 계획을 실행하는 동안 작업을 수행하려면 DeploymentPlanExecutor를 사용합니다. 이 연습에서는 SqlRestartableScriptContributor라는 DeploymentPlanModifier를 만듭니다. 이 참가자는 배포 스크립트의 일괄 처리에 IF 문을 추가하여 실행 중 오류가 발생하더라도 일괄 처리가 완료되기 전까지는 스크립트를 다시 실행할 수 있도록 합니다.

이 연습에서는 다음 주요 작업을 수행합니다.

  • DeploymentPlanModifier 형식의 배포 참가자 만들기

  • 배포 참가자 설치

  • 배포 참가자 테스트

사전 요구 사항

이 연습을 완료하려면 다음 구성 요소가 필요합니다.

  • 컴퓨터에 Visual Studio 2010 Premium 또는 Visual Studio 2010 Ultimate가 설치되어 있어야 합니다.

  • 데이터베이스 개체가 포함된 데이터베이스 프로젝트가 있어야 합니다.

  • 데이터베이스 프로젝트를 배포할 수 있는 SQL Server 인스턴스가 있어야 합니다.

참고

이 연습은 Visual Studio의 데이터베이스 기능에 이미 친숙한 사용자를 위한 것입니다. 사용자는 클래스 라이브러리를 만드는 방법, 코드 편집기를 사용하여 클래스에 코드를 추가하는 방법 등과 같은 기본 Visual Studio 개념도 알고 있어야 합니다.

배포 참가자 만들기

배포 참가자를 만들려면 다음 작업을 수행해야 합니다.

  • 클래스 라이브러리 프로젝트를 만들고 필요한 참조를 추가합니다.

  • DeploymentPlanModifier에서 상속하는 SqlRestartableScriptContributor라는 클래스를 정의합니다.

  • OnExecute 메서드를 재정의합니다.

  • private 도우미 메서드를 추가합니다.

  • 결과 어셈블리를 빌드합니다.

클래스 라이브러리 프로젝트를 만들려면

  1. MyOtherDeploymentContributor라는 Visual C# 또는 Visual Basic 클래스 라이브러리 프로젝트를 만듭니다.

  2. 솔루션 탐색기에서 프로젝트를 마우스 오른쪽 단추로 클릭한 다음 참조 추가를 클릭합니다.

  3. .NET 탭을 클릭합니다.

  4. Microsoft.Data.Schema, Microsoft.Data.Schema.Sql, Microsoft.Data.Schema.ScriptDomMicrosoft.Data.Schema.ScriptDom.Sql 항목을 강조 표시하고 확인을 클릭합니다.

    다음에는 클래스에 코드를 추가하기 시작합니다.

SqlRestartableScriptContributor 클래스를 정의하려면

  1. 코드 편집기에서 class1.cs 파일을 다음 using 문과 같이 업데이트합니다.

    using System;
    using System.Collections.Generic;
    using System.Text;
    using Microsoft.Data.Schema.Build;
    using Microsoft.Data.Schema.ScriptDom.Sql;
    using Microsoft.Data.Schema.SchemaModel;
    using System.Globalization;
    using Microsoft.Data.Schema.ScriptDom;
    using Microsoft.Data.Schema.Extensibility;
    using Microsoft.Data.Schema.Sql;
    using Microsoft.Data.Schema.Sql.Build;
    using Microsoft.Data.Schema.Sql.SchemaModel;
    
    Imports System
    Imports System.Collections.Generic
    Imports System.Text
    Imports Microsoft.Data.Schema.Build
    Imports Microsoft.Data.Schema.ScriptDom.Sql
    Imports Microsoft.Data.Schema.SchemaModel
    Imports System.Globalization
    Imports Microsoft.Data.Schema.ScriptDom
    Imports Microsoft.Data.Schema.Extensibility
    Imports Microsoft.Data.Schema.Sql
    Imports Microsoft.Data.Schema.Sql.Build
    Imports Microsoft.Data.Schema.Sql.SchemaModel
    
  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>
        [DatabaseSchemaProviderCompatibility(typeof(SqlDatabaseSchemaProvider))]
        class SqlRestartableScriptContributor : DeploymentPlanModifier
        {
        }
    
    ''' <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> 
    <DatabaseSchemaProviderCompatibility(GetType(SqlDatabaseSchemaProvider))> _
    Class SqlRestartableScriptContributor
        Inherits DeploymentPlanModifier
    End Class
    

    이제 DeploymentPlanModifier에서 상속하는 빌드 참가자가 정의되었습니다. 여기에서는 DatabaseSchemaProviderCompatibilityAttribute 특성을 사용하여 이 참가자가 SqlDatabaseSchemaProvider에서 상속하는 모든 데이터베이스 스키마 공급자와 호환됨을 나타냈습니다.

  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
    ";
            private const string DropCompletedBatchesTable = @"drop table [tempdb].[dbo].[" + CompletedBatchesVariable + "]";
    
        Private Const BatchIdColumnName As String = "BatchId"
        Private Const DescriptionColumnName As String = "Description"
    
        Private Const CompletedBatchesVariableName As String = "CompletedBatches"
        Private Const CompletedBatchesVariable As String = "$(CompletedBatches)"
        Private Const CompletedBatchesSqlCmd As String = ":setvar " & CompletedBatchesVariableName & " __completedBatches_{0}_{1}"
        Private Const TotalBatchCountSqlCmd As String = ":setvar TotalBatchCount {0}"
        Private Const CreateCompletedBatchesTable As String = vbCr & vbLf & "if OBJECT_ID(N'tempdb.dbo." & CompletedBatchesVariable & "', N'U') is null" & vbCr & vbLf & "begin" & vbCr & vbLf & vbTab & "use tempdb" & vbTab & vbCr & vbLf & vbTab & "create table [dbo].[$(CompletedBatches)]" & vbCr & vbLf & vbTab & "(" & vbCr & vbLf & vbTab & vbTab & "BatchId int primary key," & vbCr & vbLf & vbTab & vbTab & "Description nvarchar(300)" & vbCr & vbLf & vbTab & ")" & vbCr & vbLf & vbTab & "use [$(DatabaseName)]" & vbTab & vbCr & vbLf & "end" & vbCr & vbLf
        Private Const DropCompletedBatchesTable As String = "drop table [tempdb].[dbo].[" & CompletedBatchesVariable & "]"
    

    다음에는 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
            }
    
        ''' <summary> 
        ''' You override the OnExecute method to do the real work of the contributor. 
        ''' </summary> 
        ''' <param name="context"></param> 
        Protected Overloads Overrides Sub OnExecute(ByVal context As DeploymentPlanContributorContext)
            ' Replace this with the method body
        End Sub
    

    여기에서는 기본 클래스 DeploymentPlanContributorOnExecute 메서드를 재정의합니다. 이 클래스는 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);
    
            ' Obtain the first step in the Plan from the provided context 
            Dim nextStep As DeploymentStep = context.PlanHandle.Head
            Dim batchId As Integer = 0
            Dim beforePreDeploy As BeginPreDeploymentScriptStep = Nothing
    
            ' Loop through all steps in the deployment plan 
            While nextStep IsNot Nothing
                ' Increment the step pointer, saving both the current and next steps 
                Dim currentStep As DeploymentStep = nextStep
                nextStep = currentStep.[Next]
    
                '  Add additional step processing here
            End While
    
            ' if we found steps that required processing, set up a temporary table to track the work that you are doing 
            If beforePreDeploy IsNot Nothing Then
                '  Add additional post-processing here
            End If
    
            ' Cleanup and drop the table 
            Dim dropStep As New DeploymentScriptStep(DropCompletedBatchesTable)
            MyBase.AddAfter(context.PlanHandle, context.PlanHandle.Tail, dropStep)
    

    이 코드에서는 몇 개의 로컬 변수를 정의하고, 배포 계획의 모든 단계를 처리할 루프를 설정합니다. 루프가 완료된 후에는 후처리 작업을 수행해야 하며, 그런 다음 계획이 실행될 때 진행 상태를 추적하기 위해 배포 중에 만든 임시 테이블을 삭제합니다. 주요 형식은 DeploymentStep과 DeploymentScriptStep이며, 주요 메서드는 AddAfter입니다.

  3. 이제 추가 단계 처리를 추가하여 "Add additional step processing here"라는 주석을 바꿉니다.

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

    이 코드 주석에서는 처리에 대해 설명합니다. 상위 수준에서 이 코드는 사용자가 원하는 단계를 찾고 다른 단계는 무시하며 배포 후 단계의 처음 부분에 도달하면 중지합니다. 단계에 조건으로 둘러싸야 하는 문이 포함되어 있으면 추가 처리를 수행합니다. 주요 형식, 메서드 및 속성으로는 BeginPreDeploymentScriptStep, BeginPostDeploymentScriptStep, IModelElement, TSqlScript, Script, DeploymentScriptDomStepSqlPrintStep이 있습니다.

  4. 이제 "Add batch processing here"라는 주석 대신 일괄 처리 코드를 추가합니다.

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

    이 코드에서는 BEGIN/END 블록과 함께 IF 문을 만듭니다. 그런 다음 일괄 처리의 문에서 추가 처리를 수행합니다. 처리가 완료된 후에는 INSERT 문을 추가하여 스크립트 실행의 진행 상태를 추적하는 임시 테이블에 정보를 추가합니다. 마지막으로, 일괄 처리에 있던 문을 해당 문이 포함된 새 IF 문으로 바꿔 일괄 처리를 업데이트합니다.

    주요 형식, 메서드 및 속성으로는 IfStatement, BeginEndBlockStatement, StatementList, TSqlBatch, PredicateSetStatement, SetOptionsInsertStatement가 있습니다.

  5. 이제 문 처리 루프의 본문을 추가합니다. "Add additional statement processing here"라는 주석을 다음과 같이 바꿉니다.

                            TSqlStatement smnt = batch.Statements[statementIndex];
    
                            if (IsStatementEscaped(element))
                            {
                                // "escape" this statement by embedding it in a sp_executesql statement
                                string statementScript = null;
                                domStep.ScriptGenerator.GenerateScript(smnt, out statementScript);
                                ExecuteStatement spExecuteSql = CreateExecuteSql(statementScript);
                                smnt = spExecuteSql;
                            }
    
                            statementBlock.StatementList.Statements.Add(smnt);
    
                        Dim smnt As TSqlStatement = batch.Statements(statementIndex)
    
                        If IsStatementEscaped(element) Then
                            ' "escape" this statement by embedding it in a sp_executesql statement 
                            Dim statementScript As String = Nothing
                            domStep.ScriptGenerator.GenerateScript(smnt, statementScript)
                            Dim spExecuteSql As ExecuteStatement = CreateExecuteSql(statementScript)
                            smnt = spExecuteSql
                        End If
    
                        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 will track
                    // all the completed batches.  The temporary table's name has the target database name and
                    // a guid embedded in it so that:
                    // * Multiple deployment scripts targeting different DBs on the same server
                    // * Failed deployments with old tables do not conflict with more recent deployments
                    //
                    // TotalBatchCount variable - the total number of batches surrounded by if statements.  Using this
                    // variable pre/post deployment scripts can also use the CompletedBatches table to make their
                    // script rerunnable if there is an error during execution
                    StringBuilder sqlcmdVars = new StringBuilder();
                    sqlcmdVars.AppendFormat(CultureInfo.InvariantCulture, CompletedBatchesSqlCmd,
                        context.Options.TargetDatabaseName, Guid.NewGuid().ToString("D"));
                    sqlcmdVars.AppendLine();
                    sqlcmdVars.AppendFormat(CultureInfo.InvariantCulture, TotalBatchCountSqlCmd, batchId);
    
                    DeploymentScriptStep completedBatchesSetVarStep = new DeploymentScriptStep(sqlcmdVars.ToString());
                    base.AddBefore(context.PlanHandle, beforePreDeploy, completedBatchesSetVarStep);
    
                    // Create the temporary table we will use to track the work that we are doing
                    DeploymentScriptStep createStatusTableStep = new DeploymentScriptStep(CreateCompletedBatchesTable);
                    base.AddBefore(context.PlanHandle, beforePreDeploy, createStatusTableStep);
    
                ' Declare a SqlCmd variables. 
                ' 
                ' CompletedBatches variable - defines the name of the table in tempdb that will track 
                ' all the completed batches. The temporary table's name has the target database name and 
                ' a guid embedded in it so that: 
                ' * Multiple deployment scripts targeting different DBs on the same server 
                ' * Failed deployments with old tables do not conflict with more recent deployments 
                ' 
                ' TotalBatchCount variable - the total number of batches surrounded by if statements. Using this 
                ' variable pre/post deployment scripts can also use the CompletedBatches table to make their 
                ' script rerunnable if there is an error during execution 
                Dim sqlcmdVars As New StringBuilder()
                sqlcmdVars.AppendFormat(CultureInfo.InvariantCulture, CompletedBatchesSqlCmd, context.Options.TargetDatabaseName, Guid.NewGuid().ToString("D"))
                sqlcmdVars.AppendLine()
                sqlcmdVars.AppendFormat(CultureInfo.InvariantCulture, TotalBatchCountSqlCmd, batchId)
    
                Dim completedBatchesSetVarStep As New DeploymentScriptStep(sqlcmdVars.ToString())
                MyBase.AddBefore(context.PlanHandle, beforePreDeploy, completedBatchesSetVarStep)
    
                ' Create the temporary table we will use to track the work that we are doing 
                Dim createStatusTableStep As New DeploymentScriptStep(CreateCompletedBatchesTable)
                MyBase.AddBefore(context.PlanHandle, beforePreDeploy, createStatusTableStep)
    

    처리를 통해 조건문으로 둘러싸야 하는 단계를 하나 이상 발견한 경우 배포 스크립트에 문을 추가하여 SQLCMD 변수를 정의해야 합니다. 첫 번째 변수인 CompletedBatches에는 배포 스크립트에서 스크립트 실행 시 성공적으로 완료된 일괄 처리를 추적하는 데 사용되는 임시 테이블의 고유 이름이 포함됩니다. 두 번째 변수인 TotalBatchCount에는 배포 스크립트의 총 일괄 처리 수가 포함됩니다.

    주목해야 할 추가 형식, 속성 및 메서드는 다음과 같습니다.

    StringBuilder, DeploymentScriptStepAddBefore

    다음에는 이 메서드에서 호출하는 도우미 메서드를 정의합니다.

CreateExecuteSql 메서드를 추가하려면

  • 다음 코드를 추가하여 제공된 문을 EXEC sp_executesql 문으로 둘러싸는 CreateExecuteSQL 메서드를 정의합니다.

            /// <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>
            /// <param name="string"></param>
            /// <returns></returns>
            private static ExecuteStatement CreateExecuteSql(string statementScript)
            {
                // define a new Exec statement
                ExecuteStatement executeSp = new ExecuteStatement();
                ExecutableProcedureReference spExecute = new ExecutableProcedureReference();
                executeSp.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();
                procRef.Name = procName;
                spExecute.ProcedureReference = procRef;
    
                // add the script parameter, constructed from the provided statement script
                ExecuteParameter scriptParam = new ExecuteParameter();
                spExecute.Parameters.Add(scriptParam);
                scriptParam.ParameterValue = CreateLiteral(statementScript, LiteralType.UnicodeStringLiteral);
                scriptParam.Variable = CreateLiteral("@stmt", LiteralType.Variable);
                return executeSp;
            }
    
        ''' <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> 
        ''' <param name="statementScript"></param> 
        ''' <returns></returns> 
        Private Shared Function CreateExecuteSql(ByVal statementScript As String) As ExecuteStatement
            ' define a new Exec statement 
            Dim executeSp As New ExecuteStatement()
            Dim spExecute As New ExecutableProcedureReference()
            executeSp.ExecutableEntity = spExecute
    
            ' define the name of the procedure that you want to execute, in this case sp_executesql 
            Dim procName As New SchemaObjectName()
            procName.Identifiers.Add(CreateIdentifier("sp_executesql", QuoteType.NotQuoted))
            Dim procRef As New ProcedureReference()
            procRef.Name = procName
            spExecute.ProcedureReference = procRef
    
            ' add the script parameter, constructed from the provided statement script 
            Dim scriptParam As New ExecuteParameter()
            spExecute.Parameters.Add(scriptParam)
            scriptParam.ParameterValue = CreateLiteral(statementScript, LiteralType.UnicodeStringLiteral)
            scriptParam.Variable = CreateLiteral("@stmt", LiteralType.Variable)
            Return executeSp
        End Function
    

    주요 형식, 메서드 및 속성으로는 ExecuteStatement, ExecutableProcedureReference, SchemaObjectName, ProcedureReferenceExecuteParameter가 있습니다.

CreateLiteral 메서드를 추가하려면

  • 다음 코드를 추가하여 CreateLiteral 메서드를 정의합니다. 이 메서드는 제공된 문자열에서 지정된 형식의 리터럴 개체를 만듭니다.

            /// <summary>
            /// The CreateLiteral method creates a Literal object of the specified type from the provided string
            /// </summary>
            /// <param name="value"></param>
            /// <param name="type"></param>
            /// <returns></returns>
            private static Literal CreateLiteral(string value, LiteralType type)
            {
                Literal l = new Literal();
                l.Value = value;
                l.LiteralType = type;
                return l;
            }
    
        ''' <summary> 
        ''' The CreateLiteral method creates a Literal object of the specified type from the provided string 
        ''' </summary> 
        ''' <param name="value"></param> 
        ''' <param name="type"></param> 
        ''' <returns></returns> 
        Private Shared Function CreateLiteral(ByVal value As String, ByVal type As LiteralType) As Literal
            Dim l As New Literal()
            l.Value = value
            l.LiteralType = type
            Return l
        End Function
    

    주요 형식, 메서드 및 속성으로는 LiteralLiteralType가 있습니다.

CreateIdentifier 메서드를 추가하려면

  • 다음 코드를 추가하여 CreateIdentifier 메서드를 정의합니다. 이 메서드는 제공된 문자열에서 지정된 따옴표 형식을 사용하는 식별자 개체를 만듭니다.

            /// <summary>
            /// The CreateIdentifier method returns a Identifier with the specified value and quoting type
            /// </summary>
            /// <param name="value"></param>
            /// <param name="quoteType"></param>
            /// <returns></returns>
            private static Identifier CreateIdentifier(string value, QuoteType quoteType)
            {
                Identifier id = new Identifier();
                id.Value = value;
                id.QuoteType = quoteType;
                return id;
            }
    
        ''' <summary> 
        ''' The CreateIdentifier method returns a Identifier with the specified value and quoting type 
        ''' </summary> 
        ''' <param name="value"></param> 
        ''' <param name="quoteType"></param> 
        ''' <returns></returns> 
        Private Shared Function CreateIdentifier(ByVal value As String, ByVal quoteType As QuoteType) As Identifier
            Dim id As New Identifier()
            id.Value = value
            id.QuoteType = quoteType
            Return id
        End Function
    

    주요 형식, 메서드 및 속성으로는 IdentifierQuoteType이 있습니다.

CreateCompletedBatchesName 메서드를 추가하려면

  • 다음 코드를 추가하여 CreateCompletedBatchesName 메서드를 정의합니다. 이 메서드는 일괄 처리용 임시 테이블에 삽입할 이름을 만듭니다.

            /// <summary>
            /// The CreateCompletedBatchesName method creates the name that will be inserted
            /// into the temporary table for a batch.
            /// </summary>
            /// <returns></returns>
            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> 
        ''' The CreateCompletedBatchesName method creates the name that will be inserted 
        ''' into the temporary table for a batch. 
        ''' </summary> 
        ''' <returns></returns> 
        Private Shared Function CreateCompletedBatchesName() As SchemaObjectName
            Dim name As 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
        End Function
    

    주요 형식, 메서드 및 속성으로는 SchemaObjectName이 있습니다.

IsStatementEscaped 메서드를 추가하려면

  • 다음 코드를 추가하여 IsStatementEscaped 메서드를 정의합니다. 이 메서드는 모델 요소의 형식이 해당 문을 EXEC sp_executesql 문에 래핑해야만 IF 문 내에 포함할 수 있는 형식인지 확인합니다.

            /// <summary>
            /// Helper method that determins whether the specified statement needs to
            /// be escaped
            /// </summary>
            /// <param name="smnt"></param>
            /// <returns></returns>
            private static bool IsStatementEscaped(IModelElement element)
            {
                return element is ISql90Schema
                    || element is ISqlProcedure
                    || element is ISqlView
                    || element is ISqlFunction
                    || element is ISqlTrigger;
            }
    
        ''' <summary> 
        ''' Helper method that determins whether the specified statement needs to 
        ''' be escaped 
        ''' </summary> 
        ''' <param name="element"></param> 
        ''' <returns></returns> 
        Private Shared Function IsStatementEscaped(ByVal element As IModelElement) As Boolean
            Return TypeOf element Is ISql90Schema OrElse TypeOf element Is ISqlProcedure OrElse TypeOf element Is ISqlView OrElse TypeOf element Is ISqlFunction OrElse TypeOf element Is ISqlTrigger
        End Function
    

    주요 형식, 메서드 및 속성으로는 IModelElement, ISql90Schema, ISqlProcedure, ISqlView, ISqlFunctionISqlTrigger가 있습니다.

CreateBatchCompleteInsert 메서드를 추가하려면

  • 다음 코드를 추가하여 CreateBatchCompleteInsert 메서드를 정의합니다. 이 메서드는 스크립트 실행의 진행 상태를 추적하기 위해 배포 스크립트에 추가할 INSERT 문을 만듭니다.

            /// <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();
                SchemaObjectDataModificationTarget batchesCompleted = new SchemaObjectDataModificationTarget();
                insert.Target = batchesCompleted;
                batchesCompleted.SchemaObject = CreateCompletedBatchesName();
    
                // Build the columns inserted into
                Column batchIdColumn = new Column();
                batchIdColumn.Identifiers.Add(CreateIdentifier(BatchIdColumnName, QuoteType.NotQuoted));
    
                Column descriptionColumn = new Column();
                descriptionColumn.Identifiers.Add(CreateIdentifier(DescriptionColumnName, QuoteType.NotQuoted));
    
                insert.Columns.Add(batchIdColumn);
                insert.Columns.Add(descriptionColumn);
    
                // Build the values inserted
                ValuesInsertSource valueSource = new ValuesInsertSource();
                insert.InsertSource = valueSource;
    
                RowValue values = new RowValue();
                values.ColumnValues.Add(CreateLiteral(batchId.ToString(), LiteralType.Integer));
                values.ColumnValues.Add(CreateLiteral(batchDescription, LiteralType.UnicodeStringLiteral));
                valueSource.RowValues.Add(values);
    
                return insert;
            }
    
        ''' <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 Shared Function CreateBatchCompleteInsert(ByVal batchId As Integer, ByVal batchDescription As String) As InsertStatement
            Dim insert As New InsertStatement()
            Dim batchesCompleted As New SchemaObjectDataModificationTarget()
            insert.Target = batchesCompleted
            batchesCompleted.SchemaObject = CreateCompletedBatchesName()
    
            ' Build the columns inserted into 
            Dim batchIdColumn As New Column()
            batchIdColumn.Identifiers.Add(CreateIdentifier(BatchIdColumnName, QuoteType.NotQuoted))
    
            Dim descriptionColumn As New Column()
            descriptionColumn.Identifiers.Add(CreateIdentifier(DescriptionColumnName, QuoteType.NotQuoted))
    
            insert.Columns.Add(batchIdColumn)
            insert.Columns.Add(descriptionColumn)
    
            ' Build the values inserted 
            Dim valueSource As New ValuesInsertSource()
            insert.InsertSource = valueSource
    
            Dim values As New RowValue()
            values.ColumnValues.Add(CreateLiteral(batchId.ToString(), LiteralType.[Integer]))
            values.ColumnValues.Add(CreateLiteral(batchDescription, LiteralType.UnicodeStringLiteral))
            valueSource.RowValues.Add(values)
    
            Return insert
        End Function
    

    주요 형식, 메서드 및 속성으로는 InsertStatement, SchemaObjectDataModificationTarget, Column, ValuesInsertSourceRowValue가 있습니다.

CreateIfNotExecutedStatement 메서드를 추가하려면

  • 다음 코드를 추가하여 CreateIfNotExecutedStatement 메서드를 정의합니다. 이 메서드는 임시 일괄 처리에서 이 일괄 처리가 이미 실행되었음을 나타내는 테이블을 실행하는지 여부를 확인하는 IF 문을 생성합니다.

            /// <summary>
            /// This is a helper method that generates an if statement that checks the batches executed
            /// table to see if the current batch has been executed.  The if statement will look like this
            /// 
            /// if not exists(select 1 from [tempdb].[dbo].[$(CompletedBatches)] 
            ///                where BatchId = batchId)
            /// begin
            /// end
            /// </summary>
            /// <param name="batchId"></param>
            /// <returns></returns>
            private static IfStatement CreateIfNotExecutedStatement(int batchId)
            {
                // Create the exists/select statement
                ExistsPredicate existsExp = new ExistsPredicate();
                Subquery subQuery = new Subquery();
                existsExp.Subquery = subQuery;
    
                QuerySpecification select = new QuerySpecification();
                subQuery.QueryExpression = select;
                select.SelectElements.Add(CreateLiteral("1", LiteralType.Integer));
                SchemaObjectTableSource completedBatchesTable = new SchemaObjectTableSource();
                select.FromClauses.Add(completedBatchesTable);
                completedBatchesTable.SchemaObject = CreateCompletedBatchesName();
    
                WhereClause where = new WhereClause();
                select.WhereClause = where;
    
                Column batchIdColumn = new Column();
                batchIdColumn.Identifiers.Add(CreateIdentifier(BatchIdColumnName, QuoteType.SquareBracket));
                Literal batchIdValue = CreateLiteral(batchId.ToString(), LiteralType.Integer);
    
                BinaryExpression stepsEqual = new BinaryExpression();
                where.SearchCondition = stepsEqual;
                stepsEqual.BinaryExpressionType = BinaryExpressionType.Equals;
                stepsEqual.FirstExpression = batchIdColumn;
                stepsEqual.SecondExpression = batchIdValue;
    
                // Put together the rest of the statement
                IfStatement ifNotExists = new IfStatement();
                UnaryExpression notExp = new UnaryExpression();
                ifNotExists.Predicate = notExp;
                notExp.UnaryExpressionType = UnaryExpressionType.Not;
                notExp.Expression = existsExp;
    
                return ifNotExists;
            }
    
        ''' <summary> 
        ''' This is a helper method that generates an if statement that checks the batches executed 
        ''' table to see if the current batch has been executed. The if statement will look like this 
        ''' 
        ''' if not exists(select 1 from [tempdb].[dbo].[$(CompletedBatches)] 
        ''' where BatchId = batchId) 
        ''' begin 
        ''' end 
        ''' </summary> 
        ''' <param name="batchId"></param> 
        ''' <returns></returns> 
        Private Shared Function CreateIfNotExecutedStatement(ByVal batchId As Integer) As IfStatement
            ' Create the exists/select statement 
            Dim existsExp As New ExistsPredicate()
            Dim subQuery As New Subquery()
            existsExp.Subquery = subQuery
    
            Dim [select] As New QuerySpecification()
            subQuery.QueryExpression = [select]
            [select].SelectElements.Add(CreateLiteral("1", LiteralType.[Integer]))
            Dim completedBatchesTable As New SchemaObjectTableSource()
            [select].FromClauses.Add(completedBatchesTable)
            completedBatchesTable.SchemaObject = CreateCompletedBatchesName()
    
            Dim where As New WhereClause()
            [select].WhereClause = where
    
            Dim batchIdColumn As New Column()
            batchIdColumn.Identifiers.Add(CreateIdentifier(BatchIdColumnName, QuoteType.SquareBracket))
            Dim batchIdValue As Literal = CreateLiteral(batchId.ToString(), LiteralType.[Integer])
    
            Dim stepsEqual As New BinaryExpression()
            where.SearchCondition = stepsEqual
            stepsEqual.BinaryExpressionType = BinaryExpressionType.Equals
            stepsEqual.FirstExpression = batchIdColumn
            stepsEqual.SecondExpression = batchIdValue
    
            ' Put together the rest of the statement 
            Dim ifNotExists As New IfStatement()
            Dim notExp As New UnaryExpression()
            ifNotExists.Predicate = notExp
            notExp.UnaryExpressionType = UnaryExpressionType.[Not]
            notExp.Expression = existsExp
    
            Return ifNotExists
        End Function
    

    주요 형식, 메서드 및 속성으로는 IfStatement, ExistsPredicate, Subquery, SchemaObjectTableSource, WhereClause, Column, Literal, BinaryExpressionUnaryExpression이 있습니다.

GetStepInfo 메서드를 추가하려면

  1. 다음 코드를 추가하여 GetStepInfo 메서드를 정의합니다. 이 메서드의 기능은 다음과 같습니다.

            /// <summary>
            /// Helper method that generates a useful description of the step.
            /// </summary>
            /// <param name="context"></param>
            /// <param name="domStep"></param>
            /// <param name="stepDescription"></param>
            /// <param name="element"></param>
            private static void GetStepInfo(
                DeploymentPlanContributorContext context, 
                DeploymentScriptDomStep domStep,
                out string stepDescription,
                out IModelElement element)
            {
                element = null;
                CreateElementStep createStep = null;
                AlterElementStep alterStep = null;
                DropElementStep dropStep = null;
    
                // figure out what type of step we've got, and retrieve
                // either the source or target element.
                if ((createStep = domStep as CreateElementStep) != null)
                {
                    element = createStep.SourceElement;
                }
                else if ((alterStep = domStep as AlterElementStep) != null)
                {
                    element = alterStep.SourceElement;
                }
                else if ((dropStep = domStep as DropElementStep) != null)
                {
                    element = dropStep.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 = context.Source.DatabaseSchemaProvider.UserInteractionServices.GetElementName(
                        element, Microsoft.Data.Schema.ElementNameStyle.FullyQualifiedName);
    
                    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;
                }
            }
    
        ''' <summary> 
        ''' Helper method that generates a useful description of the step. 
        ''' </summary> 
        ''' <param name="context"></param> 
        ''' <param name="domStep"></param> 
        ''' <param name="stepDescription"></param> 
        ''' <param name="element"></param> 
        Private Shared Sub GetStepInfo(ByVal context As DeploymentPlanContributorContext, ByVal domStep As DeploymentScriptDomStep, ByRef stepDescription As String, ByRef element As IModelElement)
            element = Nothing
            Dim createStep As CreateElementStep = Nothing
            Dim alterStep As AlterElementStep = Nothing
            Dim dropStep As DropElementStep = Nothing
    
            ' figure out what type of step we've got, and retrieve 
            ' either the source or target element. 
            If (InlineAssignHelper(createStep, TryCast(domStep, CreateElementStep))) IsNot Nothing Then
                element = createStep.SourceElement
            ElseIf (InlineAssignHelper(alterStep, TryCast(domStep, AlterElementStep))) IsNot Nothing Then
                element = alterStep.SourceElement
            ElseIf (InlineAssignHelper(dropStep, TryCast(domStep, DropElementStep))) IsNot Nothing Then
                element = dropStep.TargetElement
            End If
    
            ' construct the step description by concatenating the type and the fully qualified 
            ' name of the associated element. 
            Dim stepTypeName As String = domStep.[GetType]().Name
            If element IsNot Nothing Then
                Dim elementName As String = context.Source.DatabaseSchemaProvider.UserInteractionServices.GetElementName(element, Microsoft.Data.Schema.ElementNameStyle.FullyQualifiedName)
    
                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
            End If
        End Sub
        Private Shared Function InlineAssignHelper(Of T)(ByRef target As T, ByVal value As T) As T
            target = value
            Return value
        End Function
    

    주목할 형식 및 메서드로는 DeploymentPlanContributorContext, DeploymentScriptDomStep, IModelElement, CreateElementStep, AlterElementStep, and DropElementStep이 있습니다.

  2. 변경 내용을 Class1.cs에 저장합니다.

    다음에는 클래스 라이브러리를 빌드합니다.

어셈블리에 서명하고 빌드하려면

  1. 프로젝트 메뉴에서 MyOtherDeploymentContributor 속성을 클릭합니다.

  2. 서명 탭을 클릭합니다.

  3. 어셈블리 서명을 클릭합니다.

  4. 강력한 이름 키 파일 선택에서 **<새로 만들기>**를 클릭합니다.

  5. 강력한 이름 키 만들기 대화 상자의 키 파일 이름에 MyRefKey를 입력합니다.

  6. (옵션) 강력한 이름 키 파일의 암호를 지정할 수 있습니다.

  7. 확인을 클릭합니다.

  8. 파일 메뉴에서 모두 저장을 클릭합니다.

  9. 빌드 메뉴에서 솔루션 빌드를 클릭합니다.

    다음에는 데이터베이스 프로젝트를 배포할 때 어셈블리가 로드되도록 어셈블리를 설치하고 등록해야 합니다.

배포 참가자 설치

배포 참가자를 설치하려면 다음 작업을 수행해야 합니다.

  • 어셈블리 및 연결된 .pdb 파일을 Extensions 폴더에 복사합니다.

  • 데이터베이스 프로젝트를 배포할 때 배포 참가자가 로드되도록 이를 등록하는 Extensions.xml 파일을 만듭니다.

MyOtherDeploymentContributor 어셈블리를 설치하려면

  1. %Program Files%\Microsoft Visual Studio 10.0\VSTSDB\Extensions 폴더에 MyExtensions라는 폴더를 만듭니다.

  2. 서명된 어셈블리(MyOtherDeploymentContributor.dll)를 %Program Files%\Microsoft Visual Studio 10.0\VSTSDB\Extensions\MyExtensions 폴더에 복사합니다.

    참고

    XML 파일을 %Program Files%\Microsoft Visual Studio 10.0\VSTSDB\Extensions 폴더에 바로 복사하지 않는 것이 좋습니다. 대신 하위 폴더를 사용하면 Visual Studio에서 제공된 다른 파일을 실수로 변경하는 것을 방지할 수 있습니다.

    다음에는 기능 확장의 한 형식인 어셈블리가 Visual Studio에 표시되도록 이를 등록해야 합니다.

MyOtherDeploymentContributor 어셈블리를 등록하려면

  1. 보기 메뉴에서 다른 창, 명령 창을 차례로 클릭하여 명령 창을 엽니다.

  2. 명령 창에서 다음 코드를 입력합니다. FilePath 대신 컴파일된 .dll 파일의 경로 및 파일 이름을 입력합니다. 이때 경로 및 파일 이름을 따옴표로 묶습니다.

    참고

    기본적으로 컴파일된 .dll 파일의 경로는 YourSolutionPath\bin\Debug 또는 YourSolutionPath\bin\Release입니다.

    ? System.Reflection.Assembly.LoadFrom(@"FilePath").FullName
    
    ? System.Reflection.Assembly.LoadFrom("FilePath").FullName
    
  3. Enter 키를 누릅니다.

  4. 결과 줄을 클립보드로 복사합니다. 이 줄은 다음과 같습니다.

    "GeneratorAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=nnnnnnnnnnnnnnnn"
    
  5. 메모장과 같은 일반 텍스트 편집기를 엽니다.

    중요

    Windows Vista 및 Microsoft Windows Server 2008에서 Program Files 폴더에 파일을 저장할 수 있도록 관리자 권한으로 편집기를 엽니다.

  6. 사용자 어셈블리 이름, 공개 키 토큰 및 확장 형식을 지정하여 다음 정보를 제공합니다.

    <?xml version="1.0" encoding="utf-8" ?> 
    <extensions assembly="" version="1" xmlns="urn:Microsoft.Data.Schema.Extensions" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:Microsoft.Data.Schema.Extensions Microsoft.Data.Schema.Extensions.xsd">
      <extension type="MyOtherDeploymentContributor.SqlRestartableScriptContributor" 
    assembly="MyOtherDeploymentContributor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=<enter key here>" enabled="true" />
    </extensions>
    

    이 XML 파일을 사용하여 DeploymentPlanExecutor에서 상속하는 클래스를 등록합니다.

  7. %Program Files%\Microsoft Visual Studio 10.0\VSTSDB\Extensions\MyExtensions 폴더에 MyOtherDeploymentContributor.extensions.xml로 파일을 저장합니다.

  8. Visual Studio를 닫습니다.

    다음에는 데이터베이스 프로젝트를 배포하여 참가자를 테스트합니다.

배포 참가자 테스트

배포 참가자를 테스트하려면 다음 작업을 수행해야 합니다.

  • MSBuild를 사용하고 적절한 매개 변수를 제공하여 데이터베이스 프로젝트를 배포합니다.

이 배포 참가자는 항상 사용하도록 설정되어 있으므로 속성을 추가하기 위해 데이터베이스 프로젝트를 수정할 필요가 없습니다.

데이터베이스 프로젝트를 배포합니다.

데이터베이스 프로젝트를 배포하고 배포 보고서를 생성하려면

  1. Visual Studio 명령 프롬프트를 엽니다. 시작 메뉴에서 모든 프로그램, Microsoft Visual Studio 2010, Visual Studio Tools, **Visual Studio 명령 프롬프트(2010)**를 차례로 클릭합니다.

  2. 명령 프롬프트에서 사용자 데이터베이스 프로젝트가 포함된 폴더로 이동합니다.

  3. 명령 프롬프트에서 다음 명령줄을 입력합니다.

    MSBuild /t:Deploy MyDatabaseProject.dbproj
    

    MyDatabaseProject를 배포할 데이터베이스 프로젝트의 이름으로 바꿉니다.

  4. 결과 배포 스크립트를 검사합니다. "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 문이 있습니다. 예를 들어 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는 IF 문 내의 EXECUTE sp_executesql 문에 포함해야 하는 문 중 하나입니다. CREATE TABLE과 같은 문은 EXECUTE sp_executesql 문이 필요하지 않으며 다음 예제와 유사합니다.

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

    참고

    대상 데이터베이스와 동일한 데이터베이스 프로젝트를 배포하면 결과 보고서에서 그다지 의미 있는 정보를 얻을 수 없습니다. 의미 있는 결과를 얻으려면 데이터베이스에 대한 변경 내용을 배포하거나 새 데이터베이스를 배포합니다.

    DeploymentPlanModifier를 사용하여 모든 배포 계획의 일괄 처리 또는 문을 추가, 제거 또는 수정할 수 있습니다.

다음 단계

배포 계획을 실행하기 전에 배포 계획을 다른 방식으로 수정하여 실험할 수 있습니다. 예를 들어 버전 번호가 연결되는 모든 데이터베이스 개체에 확장 속성을 추가하거나, 배포 스크립트의 추가 진단 출력 문 또는 주석을 추가 또는 제거하는 등의 수정 작업을 수행할 수 있습니다.

참고 항목

개념

Visual Studio의 데이터베이스 기능 확장

기타 리소스

빌드 및 배포 참가자를 사용하여 데이터베이스 빌드 및 배포 사용자 지정

연습: 데이터베이스 프로젝트 빌드를 확장하여 모델 통계 생성

연습: 데이터베이스 프로젝트 배포를 확장하여 배포 계획 분석