您可以建立部署參與者,以便在部署 SQL 專案時執行自訂動作。 您可以建立 DeploymentPlanModifier 或 DeploymentPlanExecutor。 使用 DeploymentPlanModifier 在執行計劃之前變更計劃,並使用 DeploymentPlanExecutor 在執行計劃時執行作業。 在此逐步解說中,您會建立一個 DeploymentPlanModifier,並將其命名為 SqlRestartableScriptContributor。 DeploymentPlanModifier SqlRestartableScriptContributor 會將陳述式新增至 IF 部署腳本中的批次,以讓腳本能夠重新執行,直到在執行期間發生錯誤時完成為止。
在此逐步解說中,您將完成以下主要任務:
必要條件
您需要下列元件才能完成這個逐步解說:
您必須已安裝包含 SQL Server Data Tools 且支援 C# 或 VB 開發的 Visual Studio 版本。
您必須擁有包含 SQL 物件的 SQL 專案。
可以部署資料庫專案的 SQL Server 執行個體。
注意
這個逐步解說適用於已經熟悉 SQL Server Data Tools SQL 功能的使用者。 您也應該熟悉基本的 Visual Studio 概念,例如如何建立類別程式庫,以及如何使用程式碼編輯器將程式碼新增至類別。
建立部署參與者
若要建立部署貢獻者,您必須執行下列工作:
- 建立類別庫專案並加入必要參考。
- 定義繼承自 DeploymentPlanModifier 且名稱為 SqlRestartableScriptContributor 的類別。
- 覆寫 OnExecute 方法。
- 加入私人輔助方法。
- 建置產生的組件。
建立類別庫專案
- 建立名稱為 MyOtherDeploymentContributor 的 C# 類別庫專案。
- 將 "Class1.cs" 檔案重新命名為 "SqlRestartableScriptContributor.cs"。
- 在 [方案總管] 中,以滑鼠右鍵按一下專案節點,然後選取 [新增參考]。
- 選取 [框架] 索引標籤上的 [System.ComponentModel.Composition]。
- 從專案 功能表選取管理 NuGet 套件選項。 安裝Microsoft.SqlServer.DacFx的最新穩定版本。
下一步,開始將程式碼加入至類別。
定義 SqlRestartableScriptContributor 類別
在程式碼編輯器中,更新 class1.cs 檔案,以符合下列 using陳述式:
using System; using System.Collections.Generic; using System.Globalization; using System.Text; using Microsoft.SqlServer.Dac.Deployment; using Microsoft.SqlServer.Dac.Model; using Microsoft.SqlServer.TransactSql.ScriptDom;更新類別定義以符合下列範例:
/// <summary> /// This deployment contributor modifies a deployment plan by adding if statements /// to the existing batches in order to make a deployment script able to be rerun to completion /// if an error is encountered during execution /// </summary> [ExportDeploymentPlanModifier("MyOtherDeploymentContributor.RestartableScriptContributor", "1.0.0.0")] public class SqlRestartableScriptContributor : DeploymentPlanModifier { }現在您已經定義繼承自 DeploymentPlanModifier 的部署貢獻者。 在建置和部署過程中,自訂組件是從標準擴充功能目錄載入。 部署計畫修改參與者透過ExportDeploymentPlanModifier屬性加以識別。 這個屬性是必需的,以便能夠找到參與者。 這個屬性應類似於下列函式裝飾器:
[ExportDeploymentPlanModifier("MyOtherDeploymentContributor.RestartableScriptContributor", "1.0.0.0")]新增以下成員定義:
private const string BatchIdColumnName = "BatchId"; private const string DescriptionColumnName = "Description"; private const string CompletedBatchesVariableName = "CompletedBatches"; private const string CompletedBatchesVariable = "$(CompletedBatches)"; private const string CompletedBatchesSqlCmd = @":setvar " + CompletedBatchesVariableName + " __completedBatches_{0}_{1}"; private const string TotalBatchCountSqlCmd = @":setvar TotalBatchCount {0}"; private const string CreateCompletedBatchesTable = @" if OBJECT_ID(N'tempdb.dbo." + CompletedBatchesVariable + @"', N'U') is null begin use tempdb create table [dbo].[$(CompletedBatches)] ( BatchId int primary key, Description nvarchar(300) ) use [$(DatabaseName)] end ";下一步,您會覆寫 OnExecute 方法,加入部署資料庫專案時要執行的程式碼。
覆寫 OnExecute
將下列方法加入至 SqlRestartableScriptContributor 類別:
/// <summary> /// You override the OnExecute method to do the real work of the contributor. /// </summary> /// <param name="context"></param> protected override void OnExecute(DeploymentPlanContributorContext context) { // Replace this with the method body }您可以從基底類別DeploymentPlanContributor覆寫OnExecute方法。 DeploymentPlanContributor是DeploymentPlanModifier和DeploymentPlanExecutor的基底類別。 將DeploymentPlanContributorContext物件傳遞給 OnExecute 方法,以存取任何指定的引數、來源和目標資料庫模型、部署計畫和部署選項。 在這個範例中,我們會取得部署計畫和目標資料庫名稱。
現在在 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);在這個程式碼,我們定義一些區域變數,而且設定會處理部署計畫中所有步驟的迴圈。 迴圈完成後,我們必須進行一些後處理,然後刪除我們在部署期間建立的暫存表,以追蹤計劃執行時的進度。 此處的重要類型為:DeploymentStep 和 DeploymentScriptStep。 重要的方法是 AddAfter。
現在加入額外的步驟處理,並替換掉「在此加入額外的步驟處理」的註解:
// Look for steps that mark the pre/post deployment scripts // These steps are always in the deployment plan even if the // user's project does not have a pre/post deployment script if (currentStep is BeginPreDeploymentScriptStep) { // This step marks the beginning of the predeployment script. // Save the step and move on. beforePreDeploy = (BeginPreDeploymentScriptStep)currentStep; continue; } if (currentStep is BeginPostDeploymentScriptStep) { // This is the step that marks the beginning of the post deployment script. // We do not continue processing after this point. break; } if (currentStep is SqlPrintStep) { // We do not need to put if statements around these continue; } // if we have not yet found the beginning of the pre-deployment script steps, // skip to the next step. if (beforePreDeploy == null) { // We only surround the "main" statement block with conditional // statements continue; } // Determine if this is a step that we need to surround with a conditional statement DeploymentScriptDomStep domStep = currentStep as DeploymentScriptDomStep; if (domStep == null) { // This step is not a step that we know how to modify, // so skip to the next step. continue; } TSqlScript script = domStep.Script as TSqlScript; if (script == null) { // The script dom step does not have a script with batches - skip continue; } // Loop through all the batches in the script for this step. All the // statements in the batch are enclosed in an if statement that checks // the table to ensure that the batch has not already been executed TSqlObject sqlObject; string stepDescription; GetStepInfo(domStep, out stepDescription, out sqlObject); int batchCount = script.Batches.Count; for (int batchIndex = 0; batchIndex < batchCount; batchIndex++) { // Add batch processing here }程式碼註解說明處理程序。 在高層次上,此程式碼尋找您重視的步驟,略過其他步驟,並在到達部署後步驟開頭時停止。 如果該步驟包含我們必須用條件括住的語句,我們會執行額外的處理。 重要型別、方法和屬性包含 DacFx 媒體櫃中的以下元件:BeginPreDeploymentScriptStep、BeginPostDeploymentScriptStep、TSqlObject、TSqlScript、Script、DeploymentScriptDomStep 和 SqlPrintStep。
現在,將批次處理程式碼加入,替換掉註解為 "Add batch processing here" 的部分:
// Create the if statement that contains the batch's contents IfStatement ifBatchNotExecutedStatement = CreateIfNotExecutedStatement(batchId); BeginEndBlockStatement statementBlock = new BeginEndBlockStatement(); ifBatchNotExecutedStatement.ThenStatement = statementBlock; statementBlock.StatementList = new StatementList(); TSqlBatch batch = script.Batches[batchIndex]; int statementCount = batch.Statements.Count; // Loop through all statements in the batch, embedding those in an sp_execsql // statement that must be handled this way (schemas, stored procedures, // views, functions, and triggers). for (int statementIndex = 0; statementIndex < statementCount; statementIndex++) { // Add additional statement processing here } // Add an insert statement to track that all the statements in this // batch were executed. Turn on nocount to improve performance by // avoiding row inserted messages from the server string batchDescription = string.Format(CultureInfo.InvariantCulture, "{0} batch {1}", stepDescription, batchIndex); PredicateSetStatement noCountOff = new PredicateSetStatement(); noCountOff.IsOn = false; noCountOff.Options = SetOptions.NoCount; PredicateSetStatement noCountOn = new PredicateSetStatement(); noCountOn.IsOn = true; noCountOn.Options = SetOptions.NoCount; InsertStatement batchCompleteInsert = CreateBatchCompleteInsert(batchId, batchDescription); statementBlock.StatementList.Statements.Add(noCountOn); statementBlock.StatementList.Statements.Add(batchCompleteInsert); statementBlock.StatementList.Statements.Add(noCountOff); // Remove all the statements from the batch (they are now in the if block) and add the if statement // as the sole statement in the batch batch.Statements.Clear(); batch.Statements.Add(ifBatchNotExecutedStatement); // Next batch batchId++;此程式碼會建立陳述
IF式以及區塊BEGIN/END。 然後,我們對批次中的陳述式執行額外的處理。 完成後,我們新增一個INSERT語句,將資訊新增至追蹤腳本執行進度的暫存表。 最後,將批次更新為將先前存在的陳述式替換為包含這些陳述式的新IF。 索引鍵類型、方法和屬性包括: IfStatement、 BeginEndBlockStatement、 StatementList、 TSqlBatch、 PredicateSetStatement、 SetOptions 和 InsertStatement。現在,加上陳述式處理迴圈的內容。 取代註解 "在此新增其他語句處理":
TSqlStatement smnt = batch.Statements[statementIndex]; if (IsStatementEscaped(sqlObject)) { // "escape" this statement by embedding it in a sp_executesql statement string statementScript; domStep.ScriptGenerator.GenerateScript(smnt, out statementScript); ExecuteStatement spExecuteSql = CreateExecuteSql(statementScript); smnt = spExecuteSql; } statementBlock.StatementList.Statements.Add(smnt);對於批次中的每個陳述式,如果陳述式的類型必須以
sp_executesql包裹,請相應地修改該陳述式。 然後,程式碼會將陳述式新增至您建立之BEGIN/END區塊的陳述式清單。 重要型別、方法和屬性包含 TSqlStatement 和 ExecuteStatement。最後,加入後置處理區段,以取代註解 "Add additional post-processing here":
// Declare a SqlCmd variables. // // CompletedBatches variable - defines the name of the table in tempdb that tracks all // the completed batches. The temporary table's name has the target database name and // a GUID embedded in it so that: // * Multiple deployment scripts targeting different DBs on the same server // * Failed deployments with old tables do not conflict with more recent deployments // // TotalBatchCount variable - the total number of batches surrounded by if statements. Using this // variable pre/post deployment scripts can also use the CompletedBatches table to make their // script rerunnable if there is an error during execution StringBuilder sqlcmdVars = new StringBuilder(); sqlcmdVars.AppendFormat(CultureInfo.InvariantCulture, CompletedBatchesSqlCmd, context.Options.TargetDatabaseName, Guid.NewGuid().ToString("D")); sqlcmdVars.AppendLine(); sqlcmdVars.AppendFormat(CultureInfo.InvariantCulture, TotalBatchCountSqlCmd, batchId); DeploymentScriptStep completedBatchesSetVarStep = new DeploymentScriptStep(sqlcmdVars.ToString()); base.AddBefore(context.PlanHandle, beforePreDeploy, completedBatchesSetVarStep); // Create the temporary table we use to track the work that we are doing DeploymentScriptStep createStatusTableStep = new DeploymentScriptStep(CreateCompletedBatchesTable); base.AddBefore(context.PlanHandle, beforePreDeploy, createStatusTableStep);如果處理程序找到以條件陳述式圍繞的一個或多個步驟,我們必須將陳述式加入至部署指令碼以定義 SQLCMD 變數。 變數為:
CompletedBatches包含暫存資料表的唯一名稱,部署指令碼用來追蹤指令碼執行時成功完成的批次TotalBatchCount包含部署指令碼中的批次總數
其他相關型別、屬性和方法包含:
StringBuilder、 DeploymentScriptStep 和AddBefore。下一步,您會定義這個方法呼叫的 Helper 方法。
新增協助程式方法
必須定義數個協助程式方法。 重要方法包含:
方法 Description CreateExecuteSQL定義 CreateExecuteSQL 方法,以提供的陳述式作為基礎,將它以 EXECsp_executesql陳述式包覆起來。 重要型別、方法和屬性包含以下 DacFx API 元件:ExecuteStatement、ExecutableProcedureReference、SchemaObjectName、ProcedureReference 和 ExecuteParameter。CreateCompletedBatchesName定義 CreateCompletedBatchesName 方法。 這個方法會建立插入批次暫存資料表的名稱。 鍵類型、方法和屬性包括以下 DacFx API 元件:SchemaObjectName。 IsStatementEscaped定義 IsStatementEscaped 方法。 這個方法會決定模型元素的類型是否需要將陳述式包裝在陳述式中 EXECsp_executesql,然後才能將其括在陳述式中IF。 重要類型、方法和屬性包含以下 DacFx API 元件:TSqlObject.ObjectType、ModelTypeClass 和 TypeClass 屬性,適用於下列模型類型:Schema、Procedure、View、TableValuedFunction、ScalarFunction、DatabaseDdlTrigger、DmlTrigger、ServerDdlTrigger。CreateBatchCompleteInsert定義 CreateBatchCompleteInsert 方法。 這個方法會建立 INSERT新增至部署指令碼的陳述式,以追蹤指令碼執行的進度。 金鑰類型、方法和屬性包含以下 DacFx API 元件:InsertStatement、NamedTableReference、ColumnReferenceExpression、ValuesInsertSource 和 RowValue。CreateIfNotExecutedStatement定義 CreateIfNotExecutedStatement 方法。 這個方法會產生一個 IF陳述式,檢查暫存批次執行表是否指出此批次已執行。 索引鍵類型、方法和屬性包括:IfStatement、ExistsPredicate、ScalarSubquery、NamedTableReference、WhereClause、ColumnReferenceExpression、IntegerLiteral、BooleanComparisonExpression 和 BooleanNotExpression。GetStepInfo定義 GetStepInfo 方法。 除了步驟名稱之外,這個方法還會擷取用來建立步驟指令碼的模型項目資訊。 感興趣的類型和方法包括: DeploymentPlanContributorContext、 DeploymentScriptDomStep、 TSqlObject、 CreateElementStep、 AlterElementStep 和 DropElementStep。 GetElementName建立 TSqlObject 的格式化名稱。
加入下列程式碼以定義 Helper 方法:
/// <summary> /// The CreateExecuteSql method "wraps" the provided statement script in an "sp_executesql" statement /// Examples of statements that must be so wrapped include: stored procedures, views, and functions /// </summary> private static ExecuteStatement CreateExecuteSql(string statementScript) { // define a new Exec statement ExecuteStatement executeSp = new ExecuteStatement(); ExecutableProcedureReference spExecute = new ExecutableProcedureReference(); executeSp.ExecuteSpecification = new ExecuteSpecification { ExecutableEntity = spExecute }; // define the name of the procedure that you want to execute, in this case sp_executesql SchemaObjectName procName = new SchemaObjectName(); procName.Identifiers.Add(CreateIdentifier("sp_executesql", QuoteType.NotQuoted)); ProcedureReference procRef = new ProcedureReference { Name = procName }; spExecute.ProcedureReference = new ProcedureReferenceName { ProcedureReference = procRef }; // add the script parameter, constructed from the provided statement script ExecuteParameter scriptParam = new ExecuteParameter(); spExecute.Parameters.Add(scriptParam); scriptParam.ParameterValue = new StringLiteral { Value = statementScript }; scriptParam.Variable = new VariableReference { Name = "@stmt" }; return executeSp; } /// <summary> /// The CreateIdentifier method returns a Identifier with the specified value and quoting type /// </summary> private static Identifier CreateIdentifier(string value, QuoteType quoteType) { return new Identifier { Value = value, QuoteType = quoteType }; } /// <summary> /// The CreateCompletedBatchesName method creates the name that is inserted /// into the temporary table for a batch. /// </summary> private static SchemaObjectName CreateCompletedBatchesName() { SchemaObjectName name = new SchemaObjectName(); name.Identifiers.Add(CreateIdentifier("tempdb", QuoteType.SquareBracket)); name.Identifiers.Add(CreateIdentifier("dbo", QuoteType.SquareBracket)); name.Identifiers.Add(CreateIdentifier(CompletedBatchesVariable, QuoteType.SquareBracket)); return name; } /// <summary> /// Helper method that determines whether the specified statement needs to /// be escaped /// </summary> /// <param name="sqlObject"></param> /// <returns></returns> private static bool IsStatementEscaped(TSqlObject sqlObject) { HashSet<ModelTypeClass> escapedTypes = new HashSet<ModelTypeClass> { Schema.TypeClass, Procedure.TypeClass, View.TypeClass, TableValuedFunction.TypeClass, ScalarFunction.TypeClass, DatabaseDdlTrigger.TypeClass, DmlTrigger.TypeClass, ServerDdlTrigger.TypeClass }; return escapedTypes.Contains(sqlObject.ObjectType); } /// <summary> /// Helper method that creates an INSERT statement to track a batch being completed /// </summary> /// <param name="batchId"></param> /// <param name="batchDescription"></param> /// <returns></returns> private static InsertStatement CreateBatchCompleteInsert(int batchId, string batchDescription) { InsertStatement insert = new InsertStatement(); NamedTableReference batchesCompleted = new NamedTableReference(); insert.InsertSpecification = new InsertSpecification(); insert.InsertSpecification.Target = batchesCompleted; batchesCompleted.SchemaObject = CreateCompletedBatchesName(); // Build the columns inserted into ColumnReferenceExpression batchIdColumn = new ColumnReferenceExpression(); batchIdColumn.MultiPartIdentifier = new MultiPartIdentifier(); batchIdColumn.MultiPartIdentifier.Identifiers.Add(CreateIdentifier(BatchIdColumnName, QuoteType.NotQuoted)); ColumnReferenceExpression descriptionColumn = new ColumnReferenceExpression(); descriptionColumn.MultiPartIdentifier = new MultiPartIdentifier(); descriptionColumn.MultiPartIdentifier.Identifiers.Add(CreateIdentifier(DescriptionColumnName, QuoteType.NotQuoted)); insert.InsertSpecification.Columns.Add(batchIdColumn); insert.InsertSpecification.Columns.Add(descriptionColumn); // Build the values inserted ValuesInsertSource valueSource = new ValuesInsertSource(); insert.InsertSpecification.InsertSource = valueSource; RowValue values = new RowValue(); values.ColumnValues.Add(new IntegerLiteral { Value = batchId.ToString() }); values.ColumnValues.Add(new StringLiteral { Value = batchDescription }); valueSource.RowValues.Add(values); return insert; } /// <summary> /// This is a helper method that generates an if statement that checks the batches executed /// table to see if the current batch has been executed. The if statement looks like this /// /// if not exists(select 1 from [tempdb].[dbo].[$(CompletedBatches)] /// where BatchId = batchId) /// begin /// end /// </summary> /// <param name="batchId"></param> /// <returns></returns> private static IfStatement CreateIfNotExecutedStatement(int batchId) { // Create the exists/select statement ExistsPredicate existsExp = new ExistsPredicate(); ScalarSubquery subQuery = new ScalarSubquery(); existsExp.Subquery = subQuery; subQuery.QueryExpression = new QuerySpecification { SelectElements = { new SelectScalarExpression { Expression = new IntegerLiteral { Value ="1" } } }, FromClause = new FromClause { TableReferences = { new NamedTableReference() { SchemaObject = CreateCompletedBatchesName() } } }, WhereClause = new WhereClause { SearchCondition = new BooleanComparisonExpression { ComparisonType = BooleanComparisonType.Equals, FirstExpression = new ColumnReferenceExpression { MultiPartIdentifier = new MultiPartIdentifier { Identifiers = { CreateIdentifier(BatchIdColumnName, QuoteType.SquareBracket) } } }, SecondExpression = new IntegerLiteral { Value = batchId.ToString() } } } }; // Put together the rest of the statement IfStatement ifNotExists = new IfStatement { Predicate = new BooleanNotExpression { Expression = existsExp } }; return ifNotExists; } /// <summary> /// Helper method that generates a useful description of the step. /// </summary> private static void GetStepInfo( DeploymentScriptDomStep domStep, out string stepDescription, out TSqlObject element) { element = null; // figure out what type of step we've got, and retrieve // either the source or target element. if (domStep is CreateElementStep) { element = ((CreateElementStep)domStep).SourceElement; } else if (domStep is AlterElementStep) { element = ((AlterElementStep)domStep).SourceElement; } else if (domStep is DropElementStep) { element = ((DropElementStep)domStep).TargetElement; } // construct the step description by concatenating the type and the fully qualified // name of the associated element. string stepTypeName = domStep.GetType().Name; if (element != null) { string elementName = GetElementName(element); stepDescription = string.Format(CultureInfo.InvariantCulture, "{0} {1}", stepTypeName, elementName); } else { // if the step has no associated element, just use the step type as the description stepDescription = stepTypeName; } } private static string GetElementName(TSqlObject element) { StringBuilder name = new StringBuilder(); if (element.Name.HasExternalParts) { foreach (string part in element.Name.ExternalParts) { if (name.Length > 0) { name.Append('.'); } name.AppendFormat("[{0}]", part); } } foreach (string part in element.Name.Parts) { if (name.Length > 0) { name.Append('.'); } name.AppendFormat("[{0}]", part); } return name.ToString(); }將變更儲存至 SqlRestartableScriptContributor.cs。
下一步,您會建置類別庫。
簽署並建置組件
在[專案]功能表上,選取[MyOtherDeploymentContributor 屬性]。
選取 [ 簽署 ] 索引標籤。
選取 [簽署組件]。
在 [選擇強式名稱金鑰檔案] 中,選取 [新增<]。>
在 [ 建立強式名稱金鑰 ] 對話方塊中的 [ 金鑰檔名稱],輸入 MyRefKey。
(選擇性) 您可以為強式名稱金鑰檔指定密碼。
請選擇 [確定]。
在 [File] \(檔案\) 功能表上,選取 [Save All] \(全部儲存\)。
在 [建置] 功能表上,選取 [建置方案]。
接下來,您必須安裝該元件,以便在部署 SQL 專案時能夠載入。
安裝部署參與者
若要安裝部署參與者,您必須將元件和相關聯 .pdb 的檔案複製到 Extensions 資料夾。
安裝 MyOtherDeploymentContributor 元件
接下來,您將組合資訊複製到 Extensions 目錄。 當 Visual Studio 2022 啟動時,它會識別目錄和子目錄中
%ProgramFiles%\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\Extensions\Microsoft\SQLDB\DAC的任何延伸模組,並讓它們可供使用。將 MyOtherDeploymentContributor.dll 組件檔案從輸出目錄複製到目錄
%ProgramFiles%\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\Extensions\Microsoft\SQLDB\DAC。 依預設,編譯檔案.dll的路徑是YourSolutionPath\YourProjectPath\bin\Debug或YourSolutionPath\YourProjectPath\bin\Release。
執行或測試您的部署參與者
若要執行或測試部署工具,您必須執行下列工作:
將屬性新增至您計劃建置的
.sqlproj檔案。使用 MSBuild 並提供適當的參數,部署資料庫專案。
將屬性新增至 SQL 專案 (.sqlproj) 檔案
您一定要更新 SQL 專案檔,指定要執行之參與者的識別碼。 您可以透過下列兩種方式之一來更新 SQL 專案:
您可以手動修改
.sqlproj檔案以新增所需的引數。 如果您的參與者沒有設定所需的任何參與者引數,或者您不打算在大量專案中重複使用組建參與者,您可以選擇執行此動作。 如果您選擇此選項,請在檔案中第一個 [匯入] 節點之後將下列陳述式.sqlproj新增至檔案:<PropertyGroup> <DeploymentContributors> $(DeploymentContributors); MyOtherDeploymentContributor.RestartableScriptContributor </DeploymentContributors> </PropertyGroup>第二個方法是建立包含必要參與者引數的目標檔案。 如果您對多個專案使用相同的貢獻者,並且需要貢獻者引數,這很有用,因為它包含預設值。 在此情況下,請在 MSBuild 擴充路徑建立目標檔案:
導航至
%ProgramFiles%\MSBuild。建立一個名為 "MyContributors" 的新資料夾,以儲存目標檔案。
在這個目錄中建立新檔案 "MyContributors.targets",在檔案中新增下列文字,然後儲存檔案:
<?xml version="1.0" encoding="utf-8"?> <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <DeploymentContributors>$(DeploymentContributors);MyOtherDeploymentContributor.RestartableScriptContributor</DeploymentContributors> </PropertyGroup> </Project>在您要執行參與者的任何專案的檔案內
.sqlproj,匯入目標檔案,方法是將下列陳述式.sqlproj新增至檔案中的 Import Project=“$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\SSDT\Microsoft.Data.Tools.Schema.SqlTasks.targets” /< 節點之後>的檔案:<Import Project="$(MSBuildExtensionsPath)\MyContributors\MyContributors.targets " />
遵循其中一個方法之後,您可以使用 MSBuild,傳入命令列建置的參數。
注意
您一定要更新 "DeploymentContributors" 屬性以指定您的參與者識別碼。 這是您在貢獻者來源檔案中 "ExportDeploymentPlanModifier" 屬性所用的相同識別碼。 如果沒有這個,您的貢獻者在建置專案時就不會執行。 只有當您擁有執行參與者所需的引數時,才需要更新 "ContributorArguments" 屬性。
部署資料庫專案
您的專案可以在 Visual Studio 中正常發行或部署。 開啟包含 SQL 專案的解決方案,然後從專案的右鍵選單中選擇「發行...」選項,或使用 F5 將偵錯部署到 LocalDB。 在這個範例中,會使用 [發行...] 對話方塊產生部署指令碼。
開啟 Visual Studio,並開啟包含 SQL 專案的方案。
在 [方案總管] 中,以滑鼠右鍵按一下專案,然後選擇 [發行...] 選項。
設定發行目標伺服器名稱和資料庫名稱。
從對話方塊的底部選項中選擇 [產生指令碼]。 這會建立可用於部署的指令碼。 我們可以檢查此指令碼,以驗證是否已新增
IF語句,使指令碼能夠重新啟動。檢查產生的部署指令碼。 在標示 "Pre-Deployment Script Template" 的區段之前,您應該會看到類似下列 Transact-SQL 語法的程式碼:
:setvar CompletedBatches __completedBatches_CompareProjectDB_cd1e348a-8f92-44e0-9a96-d25d65900fca :setvar TotalBatchCount 17 GO if OBJECT_ID(N'tempdb.dbo.$(CompletedBatches)', N'U') is null BEGIN USE tempdb; CREATE TABLE [dbo].[$(CompletedBatches)] ( BatchId INT PRIMARY KEY, Description NVARCHAR(300) ); USE [$(DatabaseName)]; END稍後在部署指令碼中,在每個批次周圍,您會看到原始陳述式周圍的
IF陳述式。 例如,下列 T-SQL 腳本可能會針對CREATE SCHEMA陳述式 出現:IF NOT EXISTS (SELECT 1 FROM [tempdb].[dbo].[$(CompletedBatches)] WHERE [BatchId] = 0) BEGIN EXECUTE sp_executesql @stmt = N'CREATE SCHEMA [Sales] AUTHORIZATION [dbo]'; SET NOCOUNT ON; INSERT [tempdb].[dbo].[$(CompletedBatches)] (BatchId, Description) VALUES (0, N'CreateElementStep Sales batch 0'); SET NOCOUNT OFF; ENDCREATE SCHEMA是必須包含在EXECUTEsp_executesql陳述式內的IF陳述式中的陳述式之一。 像CREATE TABLE這樣的語句不需要EXECUTEsp_executesql語句,類似於下列範例:IF NOT EXISTS (SELECT 1 FROM [tempdb].[dbo].[$(CompletedBatches)] WHERE [BatchId] = 1) BEGIN CREATE TABLE [Sales].[Customer] ( [CustomerID] INT IDENTITY (1, 1) NOT NULL, [CustomerName] NVARCHAR (40) NOT NULL, [YTDOrders] INT NOT NULL, [YTDSales] INT NOT NULL ); SET NOCOUNT ON; INSERT [tempdb].[dbo].[$(CompletedBatches)] (BatchId, Description) VALUES (1, N'CreateElementStep Sales.Customer batch 0'); SET NOCOUNT OFF; END注意
如果您部署與目標資料庫相同的資料庫專案,則產生的報告不是很有意義。 若要更有意義的結果,請將變更部署至資料庫或部署新的資料庫。
使用產生的 dacpac 檔案進行命令列部署
SQL 專案建置的輸出構件是檔案 .dacpac 。
.dacpac檔案可用來從命令列部署架構,並且可以從不同的機器(例如建置機器)進行部署。 SqlPackage 是一個命令列公用程式,具有各種選項,讓使用者能夠部署 dacpac、產生部署指令碼,以及執行其他動作。 如需詳細資訊,請參閱 SqlPackage。
注意
若要成功部署從專案中包含已定義 DeploymentContributors 屬性所建置的 dacpac,必須在所使用的計算機上安裝包含這些部署貢獻者的 DLL。 這是因為它們已經標示為部署順利完成的必要項。
若要避免此需求,請從 .sqlproj 檔案中排除部署參與者。 或者,使用 SqlPackage 與 AdditionalDeploymentContributors 參數來指定要在部署期間執行的參與者。 在您只想在特殊情況下使用協作者,例如部署到特定伺服器的時候,這非常有用。