Teilen über


Exemplarische Vorgehensweise: Erweitern der Bereitstellung eines Datenbankprojekts für die Bearbeitung des Bereitstellungsplans

Sie können Bereitstellungs-Contributors erstellen, um benutzerdefinierte Aktionen durchzuführen, wenn Sie ein SQL-Projekt bereitstellen. Sie können DeploymentPlanModifier oder DeploymentPlanExecutor erstellen. Verwenden Sie DeploymentPlanModifier, um den Plan zu ändern, bevor er ausgeführt wird, und DeploymentPlanExecutor, um Vorgänge durchzuführen, während der Plan ausgeführt wird. In dieser exemplarischen Vorgehensweise erstellen Sie einen DeploymentPlanModifier mit dem Namen SqlRestartableScriptContributor. Der DeploymentPlanModifier „SqlRestartableScriptContributor“ fügt den Batches im Bereitstellungsskript IF-Anweisungen hinzu, um ein erneutes Ausführen des Skripts zu ermöglichen, bis es fertig gestellt ist, falls während der Ausführung ein Fehler auftritt.

In dieser exemplarischen Vorgehensweise führen Sie folgende Hauptaufgaben aus:

Voraussetzungen

Zum Abschließen dieser exemplarischen Vorgehensweise benötigen Sie Folgendes:

  • Sie müssen eine Version von Visual Studio installiert haben, die SQL Server Data Tools enthält und die Entwicklung in C# unterstützt.

  • Sie müssen über ein SQL-Projekt verfügen, das SQL-Objekte enthält.

  • Eine Instanz von SQL Server, auf der Sie ein Datenbankprojekt bereitstellen können.

Hinweis

Diese exemplarische Vorgehensweise ist für Benutzer gedacht, die bereits mit den SQL-Funktionen von SQL Server Data Tools vertraut sind. Außerdem wird von Ihnen erwartet, dass Sie mit den grundlegenden Visual Studio-Konzepten vertraut sind, wie etwa dem Erstellen einer Klassenbibliothek und dem Verwenden des Code-Editor zum Hinzufügen von Code zu einer Klasse.

Einen Bereitstellungs-Contributor erstellen

Zum Erstellen eines Bereitstellungs-Contributors führen Sie folgende Aufgaben aus:

  • Erstellen Sie ein Klassenbibliotheksprojekt, und fügen Sie die erforderlichen Verweise hinzu.

  • Definieren Sie eine Klasse mit der Bezeichnung „SqlRestartableScriptContributor“, die von DeploymentPlanModifier erbt.

  • Überschreiben Sie die OnExecute-Methode.

  • Fügen Sie private Hilfemethoden hinzu.

  • Erstellen Sie die resultierende Assembly.

So erstellen Sie ein Klassenbibliotheksprojekt

  1. Erstellen Sie ein C#-Klassenbibliotheksprojekt (.NET Framework) mit der Bezeichnung MyOtherDeploymentContributor.

  2. Benennen Sie die Datei „Class1.cs“ in „SqlRestartableScriptContributor.cs“ um.

  3. Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf den Projektknoten, und klicken Sie dann auf Verweis hinzufügen.

  4. Wählen Sie auf der Registerkarte „Frameworks“ die Option System.ComponentModel.Composition aus.

  5. Wählen Sie im Menü Projekt die Option NuGet-Pakete verwalten aus. Installieren Sie die neuesten stabilen Releases für Microsoft.SqlServer.DacFx.

Beginnen Sie als Nächstes, der Klasse Code hinzuzufügen.

So definieren Sie die SqlRestartableScriptContributor-Klasse

  1. Aktualisieren Sie die im Code-Editor die class1.cs-Datei, damit sie folgenden using-Anweisungen entspricht:

    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. Aktualisieren Sie die Klassendefinition, damit sie folgendem Beispiel entspricht:

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

    Sie haben jetzt Ihren Bereitstellungs-Contributor definiert, der von DeploymentPlanModifier erbt. Während des Erstellungs- und Bereitstellungsprozesses werden benutzerdefinierte Contributors aus dem Standarderweiterungsverzeichnis geladen. Den Bereitstellungsplan ändernde Contributors werden anhand eines ExportDeploymentPlanModifier-Attributs identifiziert. Dieses Attribut muss angegeben werden, damit die Contributors ermittelt werden können. Dieses Attribut sollte in etwa wir der folgende Funktions-Decorator aussehen:

    [ExportDeploymentPlanModifier("MyOtherDeploymentContributor.RestartableScriptContributor", "1.0.0.0")]  
    
    
  3. Fügen Sie die folgenden Member-Deklarationen hinzu:

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

    Als Nächstes überschreiben Sie die OnExecute-Methode und fügen den Code hinzu, den Sie bei der Bereitstellung eines Datenbankprojekts ausführen.

So überschreiben Sie OnExecute

  1. Fügen Sie die folgende Methode der SqlRestartableScriptContributor-Klasse hinzu:

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

    Sie setzen die OnExecute-Methode aus der Basisklasse DeploymentPlanContributor außer Kraft. DeploymentPlanContributor ist die Basisklasse für DeploymentPlanModifier und DeploymentPlanExecutor. An die OnExecute-Methode wird ein DeploymentPlanContributorContext-Objekt übergeben, das Zugriff auf alle angegebenen Argumente, das Quell- und Datenbankmodell des Ziels, den Bereitstellungsplan und die Bereitstellungsoptionen bietet. In diesem Beispiel erhalten wir den Bereitstellungsplan und den Namen der Zieldatenbank.

  2. Fügen Sie jetzt die Anfänge eines Texts der OnExecute-Methode hinzu:

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

    In diesem Code definieren wir einige lokale Variablen und richten die Schleife ein, die die Verarbeitung aller Schritte im Bereitstellungsplan regelt. Nach Fertigstellung der Schleife müssen wir die Nachbearbeitung durchführen und dann die temporäre Tabelle löschen, die wir während der Bereitstellung erstellt haben, um während der Ausführung des Plans den Fortschritt nachzuverfolgen. Wichtige Typen sind: DeploymentStep und DeploymentScriptStep. Eine wichtige Methode ist „AddAfter“.

  3. Fügen Sie jetzt die Verarbeitung des zusätzlichen Schritts hinzu, und ersetzen Sie dadurch folgenden Kommentar: "Verarbeitung des zusätzlichen Schritts hier hinzufügen":

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

    Die Kommentare zum Code erklären die Verarbeitung. Dieser Code sucht auf hoher Ebene nach den Schritten, um die Sie sich kümmern, wobei andere übersprungen und angehalten werden, wenn Sie den Anfang der Schritte nach der Bereitstellung erreichen. Falls der Schritt Anweisungen enthält, die wir mit Bedingungen umgeben müssen, führen wir die weitere Verarbeitung durch. Zu den wichtigen Typen, Methoden und Eigenschaften gehören die folgenden Komponenten aus der DacFx-Bibiliothek: BeginPreDeploymentScriptStep, BeginPostDeploymentScriptStep, TSqlObject, TSqlScript, Script, DeploymentScriptDomStep und SqlPrintStep.

  4. Fügen Sie jetzt den Batchverarbeitungscode hinzu, indem Sie den Kommentar „Batchverarbeitung hier hinzufügen“ ersetzen:

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

    Mit diesem Code wird eine IF-Anweisung neben dem BEGIN/END-Block erstellt. Wir führen anschließend die weitere Verarbeitung an den Anweisungen im Batch durch. Ist diese abgeschlossen, fügen wir eine INSERT-Anweisung hinzu, um der temporären Tabelle Informationen hinzuzufügen, die den Fortschritt der Skriptausführung nachzuverfolgen. Aktualisieren Sie schließlich den Batch, und ersetzen Sie die vorherigen Anweisungen durch die neue IF-Anweisung, die diese Anweisungen enthält. Wichtige Typen, Methoden und Eigenschaften: IfStatement, BeginEndBlockStatement, StatementList, TSqlBatch, PredicateSetStatement, SetOptions und InsertStatement.

  5. Fügen Sie jetzt den Text der Anweisungsverarbeitungsschleife hinzu. Ersetzen Sie den Kommentar "Verarbeitung der zusätzlichen Anweisung hier hinzufügen":

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

    Ändern Sie jede Anweisung im Batch entsprechend, falls die Anweisung in einem Typ vorliegt, der in eine sp_executesql-Anweisung eingebunden sein muss. Vom Code wird die Anweisung dann zur Anweisungsliste für den von Ihnen erstellten BEGIN/END-Block hinzugefügt. Zu den wichtigen Typen, Methoden und Eigenschaften gehören TSqlStatement und ExecuteStatement.

  6. Fügen Sie schließlich den Nachbearbeitungsabschnitt anstelle des folgenden Kommentars hinzu: "Nachbearbeitung hier hinzufügen":

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

    Fall unsere Bearbeitung mindestens einen Schritt gefunden hat, den wir in eine bedingte Anweisung eingebunden haben, müssen wir dem Bereitstellungsskript Anweisungen hinzufügen, um SQLCMD-Variablen zu definieren. Es handelt sich um folgende Variablen:

    • CompletedBatches enthält einen eindeutigen Namen für die temporäre Tabelle, die das Bereitstellungsskript verwendet, um nachzuverfolgen, welche Batches bei der Ausführung des Skripts erfolgreich abgeschlossen werden.
    • TotalBatchCount enthält die Gesamtzahl der Batches im Bereitstellungsskript.

    Weitere interessante Typen, Eigenschaften und Methoden:

    „StringBuilder“, DeploymentScriptStep und „AddBefore“.

    Als Nächstes definieren Sie die von dieser Methode aufgerufenen Hilfemethoden.

So fügen Sie die Hilfemethoden hinzu

  • Die Anzahl der Hilfemethoden muss definiert werden. Wichtige Methoden:

    Methode Beschreibung
    CreateExecuteSQL Definieren Sie die CreateExecuteSQL-Methode so, dass eine angegebene Anweisung von einer EXEC sp_executesql-Anweisung umgeben wird. Zu den wichtigen Typen, Methoden und Eigenschaften gehören die folgenden DacFx-Komponenten: ExecuteStatement, ExecutableProcedureReference, SchemaObjectName, ProcedureReference und ExecuteParameter.
    CreateCompletedBatchesName Definieren Sie die CreateCompletedBatchesName-Methode. Von dieser Methode wird der Name erstellt, der in die temporäre Tabelle für einen Batch eingefügt wird. Zu den wichtigen Typen, Methoden und Eigenschaften gehören die folgenden DacFx-Komponenten: SchemaObjectName.
    IsStatementEscaped Definieren Sie die IsStatementEscaped-Methode. Diese Methode bestimmt, ob ein Typ des Modellelements erfordert, dass die Anweisung von einer EXEC sp_executesql-Anweisung umgeben wird, bevor sie in eine IF-Anweisung eingefügt werden kann. Zu den wichtigen Typen, Methoden und Eigenschaften gehören die folgenden DacFx-Komponenten: „TSqlObject.ObjectType“, „ModelTypeClass“ und die „TypeClass“-Eigenschaft für die folgenden Modelltypen: „Schema“, „Procedure“, „View“, „TableValuedFunction“, „ScalarFunction“, „DatabaseDdlTrigger“, „DmlTrigger“, „ServerDdlTrigger“.
    CreateBatchCompleteInsert Definieren Sie die CreateBatchCompleteInsert-Methode. Von dieser Methode wird die INSERT-Anweisung erstellt, die dem Bereitstellungsskript hinzugefügt wird, um den Fortschritt der Ausführung des Skripts nachzuverfolgen. Zu den wichtigen Typen, Methoden und Eigenschaften gehören die folgenden DacFx-Komponenten: InsertStatement, NamedTableReference, ColumnReferenceExpression, ValuesInsertSource und RowValue.
    CreateIfNotExecutedStatement Definieren Sie die CreateIfNotExecutedStatement-Methode. Von dieser Methode wird eine IF-Anweisung generiert, die prüft, ob die von den temporären Batches ausgeführte Tabelle angibt, dass dieser Batch bereits ausgeführt wurde. Zu den wichtigen Typen, Methoden und Eigenschaften zählen diese: „IfStatement“, „ExistsPredicate“, „ScalarSubquery“, „NamedTableReference“, „WhereClause“, „ColumnReferenceExpression“, „IntegerLiteral“, „BooleanComparisonExpression“ und „BooleanNotExpression“.
    GetStepInfo Definieren Sie die GetStepInfo-Methode. Mit dieser Methode werden neben dem Namen des Schritts Informationen zum Modellelement extrahiert, das zum Erstellen des Skripts dieses Schritts verwendet wird. Interessante Typen und Methoden:DeploymentPlanContributorContext, DeploymentScriptDomStep, TSqlObject, CreateElementStep, AlterElementStep und DropElementStep.
    GetElementName Erstellt einen formatierten Namen für ein TSqlObject.
  1. Fügen Sie den folgenden Code hinzu, um die Hilfemethoden zu definieren:

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

Als Nächstes erstellen Sie die Klassenbibliothek.

So signieren und erstellen Sie die Assembly

  1. Klicken Sie im Menü Projekt auf Eigenschaften von MyOtherDeploymentContributor.

  2. Klicken Sie auf die Registerkarte Signierung .

  3. Klicken Sie auf Assembly signieren.

  4. Klicken Sie unter Wählen Sie eine Schlüsseldatei mit starkem Namen auf <Neu>.

  5. Geben Sie im Dialogfeld Schlüssel für einen starken Namen erstellen unter SchlüsseldateinameMyRefKeyein.

  6. (optional) Sie können ein Kennwort für Ihre Schlüsseldatei mit starkem Namen angeben.

  7. Klicken Sie auf OK.

  8. Klicken Sie im Menü Datei auf Alle speichern.

  9. Klicken Sie im Menü Build auf Projektmappe erstellen.

    Als Nächstes müssen Sie die Assembly installieren, damit sie geladen wird, wenn Sie SQL-Projekte bereitstellen.

Einen Bereitstellungs-Contributor installieren

Zum Installieren eines Bereitstellungs-Contributors müssen Sie die Assembly und die zugehörige PDB-Datei in den Erweiterungsordner kopieren.

So installieren Sie die MyOtherDeploymentContributor-Assembly

  1. Im nächsten Schritt kopieren Sie die Assemblyinformationen in das Verzeichnis „Extensions“. Beim Start von Visual Studio 2022 werden alle Erweiterungen im Verzeichnis %Programme%\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\Extensions\Microsoft\SQLDB\DAC und in dessen Unterverzeichnissen identifiziert und für die Verwendung zur Verfügung gestellt.

  2. Kopieren Sie die MyOtherDeploymentContributor.dll-Assemblydatei aus dem Ausgabeverzeichnis in das Verzeichnis %Programme%\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\Extensions\Microsoft\SQLDB\DAC. Der Pfad Ihrer kompilierten DLL-Datei lautet standardmäßig IhrProjektmappenpfad\IhrProjektpfad\bin\Debug oder IhrProjektmappenpfad\IhrProjektpfad\bin\Release.

Ausführen oder Testen eines Bereitstellungs-Contributors

Zum Ausführen oder Testen eines Bereitstellungs-Contributors führen Sie folgende Aufgaben aus:

  • Fügen Sie der SQLPROJ-Datei, die Sie zu erstellen planen, Eigenschaften hinzu.

  • Stellen Sie das Datenbankprojekt mithilfe von MSBuild und durch Angeben der entsprechenden Parameter bereit.

So fügen Sie der SQL Project (.sqlproj)-Datei Eigenschaften hinzu

Sie müssen die SQL-Projektdatei immer aktualisieren, um die ID der Contributor festzulegen, die Sie ausführen möchten. Sie können das SQL-Projekt auf eine von zwei Arten aktualisieren:

  1. Sie können die SQLPROJ-Datei manuell bearbeiten, um die erforderlichen Argumente hinzuzufügen. Sie können sich dafür entscheiden, falls Ihr Contributor über keine Contributor-Argumente verfügt, die zur Konfiguration erforderlich sind, oder falls Sie den Build-Contributor nicht für eine große Anzahl von Projekten wiederverwenden möchten. Falls Sie diese Option wählen, fügen Sie der SQLPROJ-Datei nach dem ersten Importknoten in der Datei die folgenden Anweisungen hinzu:

    <PropertyGroup>  
      <DeploymentContributors>  
        $(DeploymentContributors); MyOtherDeploymentContributor.RestartableScriptContributor  
      </DeploymentContributors>  
    </PropertyGroup>  
    
  2. Die zweite Methode ist das Erstellen einer Zieledatei, die die erforderlichen Contributorargumente enthält. Diese ist hilfreich, falls Sie denselben Contributor für mehrere Projekte verwenden und Contributorargumente erforderlich sind, da sie die Standardwerte enthält. Erstellen Sie in diesem Fall eine Zieledatei im MSBuild-Erweiterungspfad:

    1. Navigieren Sie zu %Programme%\MSBuild.

    2. Erstellen Sie einen neuen Ordner namens „MyContributors“, in dem Ihre Zieldateien gespeichert werden.

    3. Erstellen Sie in diesem Verzeichnis eine neue Datei namens „MyContributors.targets“, fügen Sie den folgenden Text hinzu, und speichern Sie die Datei:

      <?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. Importieren Sie innerhalb der SQLPROJ-Datei für ein beliebiges Projekt (in dem Sie Contributors ausführen möchten) die Zieldatei, indem Sie die folgende Anweisung nach dem Knoten <Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\SSDT\Microsoft.Data.Tools.Schema.SqlTasks.targets" /> in die SQLPROJ-Datei einfügen:

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

Nach dem Durchführen einer dieser Vorgehensweisen können Sie MSBuild verwenden, um die Parameter für Befehlszeilenbuilds zu übertragen.

Hinweis

Sie müssen die Eigenschaft „DeploymentContributors“ stets aktualisieren, damit Ihre Contributor-ID angegeben wird. Dies ist die gleiche ID, die auch im Attribut „ExportDeploymentPlanModifier“ in Ihrer Contributorquelldatei verwendet wird. Ohne diese wird Ihr Contributor nicht ausgeführt, wenn Sie das Projekt erstellen. Die Eigenschaft „ContributorArguments“ muss nur aktualisiert werden, wenn Argumente erforderlich sind, damit Ihr Contributor ausgeführt werden kann.

Das Datenbankprojekt bereitstellen

So stellen Sie Ihr SQL-Projekt bereit und generieren einen Bereitstellungsbericht

  • Ihr Projekt kann in Visual Studio normal veröffentlicht oder bereitgestellt werden. Öffnen Sie einfach eine Projektmappe, die Ihr SQL-Projekt enthält, und wählen Sie im Kontextmenü des Projekts die Option „Veröffentlichen...“ aus, oder verwenden Sie F5 für eine Debug-Bereitstellung in LocalDB. In diesem Beispiel wird das Dialogfeld „Veröffentlichen...“ verwendet, um ein Bereitstellungsskript zu generieren.

    1. Öffnen Sie Visual Studio und die Projektmappe, die Ihr SQL-Projekt enthält.

    2. Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf das Projekt, und wählen Sie die Option Veröffentlichen... aus.

    3. Legen Sie den Servernamen und Datenbanknamen fest, auf dem veröffentlicht werden soll.

    4. Wählen Sie aus den Optionen unten im Dialogfeld Skript generieren. Dadurch wird ein Skript erstellt, das für die Bereitstellung verwendet werden kann. Wir können dieses Skript untersuchen, um zu prüfen, ob unsere IF-Anweisungen hinzugefügt wurden, um die Startbarkeit des Skripts wiederherzustellen.

    5. Untersuchen Sie das resultierende Bereitstellungsskript. Direkt vor dem Abschnitt mit der Bezeichnung „Vorlage für ein Skript vor der Bereitstellung“ sehen Sie ein Element, das folgender Transact-SQL-Syntax ähnelt:

      :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  
      
      

      Später im Bereitstellungsskript sehen Sie um jeden Batch herum eine IF-Anweisung, die die ursprüngliche Anweisung umgibt. Das folgende T-SQL-Skript könnte beispielsweise für eine CREATE SCHEMA-Anweisung angezeigt werden:

      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  
      
      

      Beachten Sie, dass CREATE SCHEMA eine der Anweisungen ist, die innerhalb der IF-Anweisung in eine EXECUTE sp_executesql-Anweisung eingeschlossen werden muss. Anweisungen, wie etwa CREATE TABLE, benötigen die EXECUTE sp_executesql-Anweisung nicht und ähneln folgendem Beispiel:

      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  
      
      

      Hinweis

      Falls Sie ein Datenbankprojekt bereitstellen, das mit der Zieldatenbank identisch ist, ist der daraus resultierende Bericht nicht sehr aussagekräftig. Weitere aussagekräftige Ergebnisse erhalten Sie, wenn Sie Änderungen an einer Datenbank bereitstellen oder eine neue Datenbank bereitstellen.

Befehlszeilenbereitstellung mit der generierten DACPAC-Datei

Das Ouput-Artefakt aus einem SQL-Projektbuild ist eine dacpac-Datei. Eine DACPAC-Datei kann zum Bereitstellen des Schemas über die Befehlszeile verwendet werden, das die Bereitstellung über einen anderen Computer als einen Buildcomputer aktivieren kann. SqlPackage ist ein Befehlszeilen-Hilfsprogramm, das Benutzern unter anderem die Bereitstellung einer DACPAC-Datei oder das Generieren eines Bereitstellungsskripts ermöglicht. Weitere Informationen finden Sie unter SqlPackage.exe.

Hinweis

Zur erfolgreichen Bereitstellung von DACPAC-Dateien, die über Projekte mit definierter DeploymentContributors-Eigenschaft erstellt wurden, müssen die DLLs, die Ihre Bereitstellungs-Contributors enthalten, auf dem verwendeten Computer installiert sein. Dies ist der Fall, weil sie für eine erfolgreiche Bereitstellung als erforderlich markiert wurden.

Sie vermeiden diese Anforderung, indem Sie den Bereitstellungs-Contributor von der SQLPROJ-Datei ausschließen. Legen Sie stattdessen Contributors fest, die während der Bereitstellung mithilfe des SqlPackage mit dem AdditionalDeploymentContributors-Parameter ausgeführt werden sollen. Dies ist hilfreich, wenn Sie einen Contributor nur bei besonderen Umständen verwenden möchten, wie etwa beim Bereitstellen eines spezifischen Servers.

Nächste Schritte

Sie können mit anderen Änderungen an Bereitstellungsplänen experimentieren, bevor diese ausgeführt werden. Andere Änderungstypen:

  • Hinzufügen einer erweiterten Eigenschaft zu allen Datenbankobjekten, denen eine Versionsnummer zugeordnet ist.

  • Hinzufügen oder Entfernen weiterer Diagnosedruckanweisungen oder Kommentare zu/aus Bereitstellungsskripts.

Weitere Informationen

Anpassen der Datenbankerstellung und -bereitstellung durch Erstellungs- und Bereitstellungs-Contributors
Exemplarische Vorgehensweise: Erweitern von Datenbankprojekten zum Generieren von Modellstatistiken
Exemplarische Vorgehensweise: Erweitern der Bereitstellung eines Datenbankprojekts zum Analysieren des Bereitstellungsplans