本逐步說明主題會示範用來建立 SQL 程式碼分析規則的步驟。 此逐步解說中建立的規則可用來避免在預存程序、觸發程序及函式中使用 WAITFOR DELAY 陳述式。
在此逐步解說中,您將使用下列程序,為 Transact-SQL 靜態程式碼分析建立自訂規則:
建立類別庫、啟用該專案的簽署,並加入必要的參考。
建立兩個協助程式 C# 類別。
建立 C# 自訂規則類別。
建立用來註冊組件的 XML 檔。
將產生的 DLL 和您建立的 XML 檔複製到 Extensions 目錄以註冊。
確認新的程式碼分析規則已經就緒。
必要條件
您必須安裝 Visual Studio Premium 或 Visual Studio Ultimate 才能完成本逐步解說。
為 SQL 建立自訂程式碼分析規則
首先,您將建立類別庫。
若要建立類別庫
按一下 [檔案] 功能表上的 [新增],然後按一下 [專案]。
在 [新增專案] 對話方塊的 [已安裝的範本] 清單中,按一下 [Visual C#]。
選取詳細資料窗格中的 [類別庫]。
在 [名稱] 文字方塊中輸入 SampleRules,然後按一下 [確定]。
下一步,您將會簽署專案。
若要啟用專案的簽署
在 [方案總管] 中選取 SampleRules 專案節點,從 [專案] 功能表上,按一下 [屬性] (或在 [方案總管] 中的專案節點上按一下滑鼠右鍵,然後按一下 [屬性])。
按一下 [簽署] 索引標籤。
選取 [簽署組件] 核取方塊。
指定新金鑰檔。 在 [選擇強式名稱金鑰檔] 下拉式清單中,選取 [<新增>]。
[建立強式名稱金鑰] 對話方塊隨即出現。 如需詳細資訊,請參閱建立強式名稱金鑰對話方塊。
在 [建立強式名稱金鑰] 對話方塊的 [名稱] 文字方塊中,為新金鑰檔輸入 SampleRulesKey。 您不需要為此逐步解說提供密碼。 如需詳細資訊,請參閱管理組件和資訊清單簽署。
接下來將必要參考加入至專案。
若要將適用的參考加入至專案
在 [方案總管] 中,選取 SampleRules 專案。
在 [專案] 功能表上,按一下 [加入參考]。
[加入參考] 對話方塊隨即開啟。 如需詳細資訊,請參閱 HOW TO:在 Visual Studio 中新增或移除參考。
選取 [.NET] 索引標籤。
在 [元件名稱] 欄中,找到下列元件:
注意事項若要選取多個元件,請在按一下每個元件時按住 CTRL。
當完成選取所需的全部元件後,按一下 [確定]。
選取的參考將出現在 [方案總管] 中專案的 [參考] 節點底下。
建立自訂程式碼分析規則支援類別
建立規則本身的類別之前,您會將訪問項類別和協助程式類別加入至專案。
秘訣 |
|---|
這些類別有助於建立其他自訂規則。 |
您必須定義的第一個類別是衍生自 TSqlConcreteFragmentVisitor 的 WaitForDelayVisitor 類別。 這個類別可用來存取模型中的 WAITFOR DELAY 陳述式。
若要定義 WaitForDelayVisitor 類別
在 [方案總管] 中,選取 SampleRules 專案。
選取 [專案] 功能表上的 [加入類別]。
[加入新項目] 對話方塊隨即出現。
在 [名稱] 文字方塊中輸入 WaitForDelayVisitor.cs,然後按一下 [加入] 按鈕。
WaitForDelayVisitor.cs 檔案隨即加入至 [方案總管] 中的專案。
開啟 WaitForDelayVisitor.cs 檔,然後更新其內容以與下列程式碼相符:
using System.Collections.Generic; using Microsoft.Data.Schema.ScriptDom.Sql; namespace SampleRules { class WaitForDelayVistor { } }在類別宣告中,將存取修飾詞變更為 internal 並從 TSqlConcreteFragmentVisitor 衍生類別:
internal class WaitForDelayVisitor : TSqlConcreteFragmentVisitor { }加入下列程式碼以定義 List 成員變數:
private List<WaitForStatement> _waitForDelayStatments;加入下列程式碼以定義類別建構函式:
#region ctor public WaitForDelayVisitor() { _waitForDelayStatments = new List<WaitForStatement>(); } #endregion加入下列程式碼以定義唯讀的 WaitForDelayStatements 屬性:
#region properties public List<WaitForStatement> WaitForDelayStatements { get { return _waitForDelayStatments; } } #endregion加入下列程式碼以覆寫 ExplicitVisit 方法:
#region overrides public override void ExplicitVisit(WaitForStatement node) { // We are only interested in WAITFOR DELAY occurrences if (node.WaitForOption == WaitForOption.Delay) { _waitForDelayStatments.Add(node); } } #endregion這個方法會造訪模型中的 WAITFOR 陳述式,然後將具有 DELAY 選項的陳述式加入至 WAITFOR DELAY 陳述式清單。 這裡參考的金鑰類別是 WaitForStatement。
在 [檔案] 功能表上,按一下 [儲存]。
第二個類別是 SqlRuleUtils.cs,您稍後在本逐步解說的建立自訂程式碼分析規則類別一節中建立的自訂程式碼分析規則類別,會使用其中包含的一些公用程式方法。 這些方法包括下列各項:
GetElementName 用於取得模型項目的逸出完整名稱。
UpdateProblemPosition 用於計算行與欄資訊。
ReadFileContent 用於從讀取檔案內容。
GetElementSourceFile 用於取得原始程式檔。
ComputeLineColumn 用於將 ScriptDom 的位移轉換成指令碼檔中的行和欄。
若要將 SqlRuleUtils.cs 檔加入至專案
在 [方案總管] 中,選取 SampleRules 專案。
選取 [專案] 功能表上的 [加入類別]。
[加入新項目] 對話方塊隨即出現。
在 [名稱] 文字方塊中輸入 SqlRuleUtils.cs,然後按一下 [加入] 按鈕。
SqlRuleUtils.cs 檔會加入至 [方案總管] 中的專案。
開啟 SqlRuleUtils.cs 檔,並將下列 using 陳述式加入至檔案:
using System; using System.Diagnostics; using System.IO; using Microsoft.Data.Schema.SchemaModel; using Microsoft.Data.Schema.Sql.SchemaModel; using Microsoft.Data.Schema.StaticCodeAnalysis; using Microsoft.Data.Schema; namespace SampleRules { }在 SqlRuleUtils 類別宣告中,將存取修飾詞變更為 public static:
public static class SqlRuleUtils { }加入下列程式碼來建立 GetElementName 方法,這個方法會使用 SqlSchemaModel 和 ISqlModelElement 做為輸入參數:
/// <summary> /// Get escaped fully qualified name of a model element /// </summary> /// <param name="sm">schema model</param> /// <param name="element">model element</param> /// <returns>name of the element</returns> public static string GetElementName(SqlSchemaModel sm, ISqlModelElement element) { return sm.DatabaseSchemaProvider.UserInteractionServices.GetElementName(element, ElementNameStyle.EscapedFullyQualifiedName); }加入下列程式碼來建立 ReadFileContent 方法:
/// <summary> /// Read file content from a file. /// </summary> /// <param name="filePath"> file path </param> /// <returns> file content in a string </returns> public static string ReadFileContent(string filePath) { // Verify that the file exists first. if (!File.Exists(filePath)) { Debug.WriteLine(string.Format("Cannot find the file: '{0}'", filePath)); return string.Empty; } string content; using (StreamReader reader = new StreamReader(filePath)) { content = reader.ReadToEnd(); reader.Close(); } return content; }加入下列程式碼來建立 GetElementSourceFile 方法,這個方法會使用 IModelElement 做為輸入參數以及使用 String 來擷取檔案名稱。 這個方法會將 IModelElement 轉型為 IScriptSourcedModelElement,然後使用 ISourceInformation 從模型項目判斷指令碼檔路徑。
/// <summary> /// Get the corresponding script file path from a model element. /// </summary> /// <param name="element">model element</param> /// <param name="fileName">file path of the scripts corresponding to the model element</param> /// <returns></returns> private static Boolean GetElementSourceFile(IModelElement element, out String fileName) { fileName = null; IScriptSourcedModelElement scriptSourcedElement = element as IScriptSourcedModelElement; if (scriptSourcedElement != null) { ISourceInformation elementSource = scriptSourcedElement.PrimarySource; if (elementSource != null) { fileName = elementSource.SourceName; } } return String.IsNullOrEmpty(fileName) == false; }加入下列程式碼來建立 ComputeLineColumn 方法:
/// This method converts offset from ScriptDom to line\column in script files. /// A line is defined as a sequence of characters followed by a carriage return ("\r"), /// a line feed ("\n"), or a carriage return immediately followed by a line feed. public static bool ComputeLineColumn(string text, Int32 offset, Int32 length, out Int32 startLine, out Int32 startColumn, out Int32 endLine, out Int32 endColumn) { const char LF = '\n'; const char CR = '\r'; // Setting the initial value of line and column to 0 since VS auto-increments by 1. startLine = 0; startColumn = 0; endLine = 0; endColumn = 0; int textLength = text.Length; if (offset < 0 || length < 0 || offset + length > textLength) { return false; } for (int charIndex = 0; charIndex < length + offset; ++charIndex) { char currentChar = text[charIndex]; Boolean afterOffset = charIndex >= offset; if (currentChar == LF) { ++endLine; endColumn = 0; if (afterOffset == false) { ++startLine; startColumn = 0; } } else if (currentChar == CR) { // CR/LF combination, consuming LF. if ((charIndex + 1 < textLength) && (text[charIndex + 1] == LF)) { ++charIndex; } ++endLine; endColumn = 0; if (afterOffset == false) { ++startLine; startColumn = 0; } } else { ++endColumn; if (afterOffset == false) { ++startColumn; } } } return true; }加入下列程式碼來建立 UpdateProblemPosition 方法,這個方法會使用 DataRuleProblem 做為輸入參數:
/// <summary> /// Compute the start Line/Col and the end Line/Col to update problem info /// </summary> /// <param name="problem">problem found</param> /// <param name="offset">offset of the fragment having problem</param> /// <param name="length">length of the fragment having problem</param> public static void UpdateProblemPosition(DataRuleProblem problem, int offset, int length) { if (problem.ModelElement != null) { String fileName = null; int startLine = 0; int startColumn = 0; int endLine = 0; int endColumn = 0; bool ret = GetElementSourceFile(problem.ModelElement, out fileName); if (ret) { string fullScript = ReadFileContent(fileName); if (fullScript != null) { if (ComputeLineColumn(fullScript, offset, length, out startLine, out startColumn, out endLine, out endColumn)) { problem.FileName = fileName; problem.StartLine = startLine + 1; problem.StartColumn = startColumn + 1; problem.EndLine = endLine + 1; problem.EndColumn = endColumn + 1; } else { Debug.WriteLine("Could not compute line and column"); } } } } }在 [檔案] 功能表上,按一下 [儲存]。
接下來,您將加入資源檔,該資源檔會定義規則名稱、規則說明,以及在規則組態介面中,規則會出現在底下的分類。
若要加入資源檔和三個資源字串
在 [方案總管] 中,選取 SampleRules 專案。
在 [專案] 功能表中選取 [加入新項目]。
[加入新項目] 對話方塊隨即出現。
按一下 [已安裝的範本] 清單中的 [一般]。
按一下詳細資料窗格中的 [資源檔]。
在 [名稱] 中,輸入 SampleRuleResource.resx。
資源編輯器隨即出現,但尚未定義任何資源。
定義如下的三個資源字串:
名稱
值
AvoidWaitForDelay_ProblemDescription
在 {0} 中找到 WAITFOR DELAY 陳述式。
AvoidWaitForDelay_RuleName
避免在預存程序、函式和觸發程序中使用 WaitFor Delay 陳述式。
CategorySamples
SamplesCategory
按一下 [檔案] 功能表上的 [儲存 SampleRuleResource.resx]。
接下來,您將定義一個類別,這個類別會參考資源檔中的資源,而 Visual Studio 會使用這些資源在使用者介面中顯示您的規則的相關資訊。
若要定義 SampleConstants 類別
在 [方案總管] 中,選取 SampleRules 專案。
選取 [專案] 功能表上的 [加入類別]。
[加入新項目] 對話方塊隨即出現。
在 [名稱] 文字方塊中輸入 SampleRuleConstants.cs,然後按一下 [加入] 按鈕。
SampleRuleConstants.cs 檔隨即加入至 [方案總管] 中的專案。
開啟 SampleRuleConstants.cs 檔,並將下列 using 陳述式加入至檔案:
namespace SampleRules { internal class SampleConstants { public const string NameSpace = "SamplesRules"; public const string ResourceBaseName = "SampleRules.SampleRuleResource"; public const string CategorySamples = "CategorySamples"; public const string AvoidWaitForDelayRuleId = "SR1004"; public const string AvoidWaitForDelay_RuleName = "AvoidWaitForDelay_RuleName"; public const string AvoidWaitForDelay_ProblemDescription = "AvoidWaitForDelay_ProblemDescription"; } }在 [檔案] 功能表上,按一下 [儲存]。
建立自訂程式碼分析規則類別
現在您已經加入自訂程式碼分析規則將使用的協助程式類別,接下來就是建立自訂規則類別並將它命名為 AvoidWaitForDelayRule。 AvoidWaitForDelayRule 自訂規則將用來協助資料庫開發人員避免在預存程序、觸發程序及函式中使用 WAITFOR DELAY 陳述式。
若要建立 AvoidWaitForDelayRule 類別
在 [方案總管] 中,選取 SampleRules 專案。
在 [專案] 功能表上,選取 [新增資料夾]。
新資料夾隨即出現在 [方案總管] 中。 將資料夾命名為 AvoidWaitForDelayRule。
在 [方案總管] 中,確認選取 AvoidWaitForDelayRule 資料夾。
選取 [專案] 功能表上的 [加入類別]。
[加入新項目] 對話方塊隨即出現。
在 [名稱] 文字方塊中輸入 AvoidWaitForDelayRule.cs,然後按一下 [加入] 按鈕。
AvoidWaitForDelayRule.cs 檔會加入至 [方案總管] 中專案的 AvoidWaitForDelayRule 資料夾。
開啟 AvoidWaitForDelayRule.cs 檔,並將下列 using 陳述式加入至檔案:
using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using Microsoft.Data.Schema.Extensibility; using Microsoft.Data.Schema.SchemaModel; using Microsoft.Data.Schema.ScriptDom.Sql; using Microsoft.Data.Schema.Sql.SchemaModel; using Microsoft.Data.Schema.Sql; using Microsoft.Data.Schema.StaticCodeAnalysis; namespace SampleRules { public class AvoidWaitForDelayRule { } }
注意事項您必須將命名空間名稱從 SampleRules.AvoidWaitForDelayRule 變更為 SampleRules。
在 AvoidWaitForDelayRule 類別宣告中,將存取修飾詞變更為 public:
/// <summary> /// This is a SQL rule which returns a warning message /// whenever there is a WAITFOR DELAY statement appears inside a subroutine body. /// This rule only applies to SQL stored procedures, functions and triggers. /// </summary> public class AvoidWaitForDelayRule從 StaticCodeAnalysisRule 基底類別衍生 AvoidWaitForDelayRule 類別:
public class AvoidWaitForDelayRule : StaticCodeAnalysisRule將 DatabaseSchemaProviderCompatibilityAttribute、DataRuleAttribute 和 SupportedElementTypeAttribute 加入至您的類別。 如需擴充功能相容性的詳細資訊,請參閱擴充 Visual Studio 的資料庫功能。
[DatabaseSchemaProviderCompatibility(typeof(SqlDatabaseSchemaProvider))] [DataRuleAttribute( SampleConstants.NameSpace, SampleConstants.AvoidWaitForDelayRuleId, SampleConstants.ResourceBaseName, SampleConstants.AvoidWaitForDelay_RuleName, SampleConstants.CategorySamples, DescriptionResourceId = SampleConstants.AvoidWaitForDelay_ProblemDescription)] [SupportedElementType(typeof(ISqlProcedure))] [SupportedElementType(typeof(ISqlTrigger))] [SupportedElementType(typeof(ISqlFunction))] public class AvoidWaitForDelayRule : StaticCodeAnalysisRuleDataRuleAttribute 指定當您設定資料庫程式碼分析規則時,會出現在 Visual Studio 中的資訊。 SupportedElementTypeAttribute 則定義要套用這個規則的項目類型。 在這個案例中,規則會套用至預存程序、觸發程序和函式。
加入 Analyze 方法的覆寫,這個方法會使用 DataRuleSetting 和 DataRuleExecutionContext 做為輸入參數。 這個方法會傳回潛在問題的清單。
此方法會從內容參數取得 IModelElement 和 TSqlFragment。 會從模型項目取得 SqlSchemaModel 和 ISqlModelElement。 然後會使用 WaitForDelayVisitor 類別來取得模型中所有 WAITFOR DELAY 陳述式的清單。
對於該清單中的每個 WaitForStatement,會建立 DataRuleProblem。
#region Overrides /// <summary> /// Analyze the model element /// </summary> public override IList<DataRuleProblem> Analyze(DataRuleSetting ruleSetting, DataRuleExecutionContext context) { List<DataRuleProblem> problems = new List<DataRuleProblem>(); IModelElement modelElement = context.ModelElement; // this rule does not apply to inline table-valued function // we simply do not return any problem if (modelElement is ISqlInlineTableValuedFunction) { return problems; } // casting to SQL specific SqlSchemaModel sqlSchemaModel = modelElement.Model as SqlSchemaModel; Debug.Assert(sqlSchemaModel!=null, "SqlSchemaModel is expected"); ISqlModelElement sqlElement = modelElement as ISqlModelElement; Debug.Assert(sqlElement != null, "ISqlModelElement is expected"); // Get ScriptDom for this model element TSqlFragment sqlFragment = context.ScriptFragment as TSqlFragment; Debug.Assert(sqlFragment != null, "TSqlFragment is expected"); // visitor to get the ocurrences of WAITFOR DELAY statements WaitForDelayVisitor visitor = new WaitForDelayVisitor(); sqlFragment.Accept(visitor); List<WaitForStatement> waitforDelayStatements = visitor.WaitForDelayStatements; // Create problems for each WAITFOR DELAY statement found foreach (WaitForStatement waitForStatement in waitforDelayStatements) { DataRuleProblem problem = new DataRuleProblem(this, String.Format(CultureInfo.CurrentCulture, this.RuleProperties.Description, SqlRuleUtils.GetElementName(sqlSchemaModel, sqlElement)), sqlElement); SqlRuleUtils.UpdateProblemPosition(problem, waitForStatement.StartOffset, waitForStatement.FragmentLength); problems.Add(problem); } return problems; } #endregion在 [檔案] 功能表上,按一下 [儲存]。
下一步,您將會建置專案。
若要建置專案
- 在 [建置] 功能表中,按一下 [建置方案]。
接下來,您將收集專案中產生的組件資訊,包括版本、文化特性和 PublicKeyToken。
若要收集組件資訊
按一下 [檢視] 功能表上的 [其他視窗],然後按一下 [命令視窗] 開啟 [命令視窗]。
在 [命令] 視窗中輸入下列程式碼。 將 FilePath 替代為已編譯之 .dll 檔案的路徑和檔案名稱。 請在路徑和檔案名稱周圍加上引號。
注意事項根據預設,FilePath 是 Projects\SampleRules\SampleRules\bin\Debug\YourDLL 或 Projects\SampleRules\SampleRules\bin\Release\YourDLL。
? System.Reflection.Assembly.LoadFrom(@"FilePath")請按 ENTER 鍵。 該行應該看起來如下,其中含有您的特定 PublicKeyToken:
"SampleRules, Version=1.0.0.0, Culture=neutral, PublicKeyToken=nnnnnnnnnnnnnnnn"標記或複製此組件資訊;此資訊將用於下一個程序。
接下來,您將使用在上一個程序中收集的組件資訊來建立 XML 檔案。
若要建立 XML 檔
在 [方案總管] 中,選取 SampleRules 專案。
在 [專案] 功能表中選取 [加入新項目]。
在 [範本] 窗格中,找出並選取 [XML 檔] 項目。
在 [名稱] 文字方塊中輸入 SampleRules.Extensions.xml,然後按一下 [加入] 按鈕。
SampleRules.Extensions.xml 檔會加入至 [方案總管] 中的專案。
開啟並更新 SampleRules.Extensions.xml 檔,以與下列 XML 相符。 取代您在前面程序擷取的值取代組件版本、文化特性和 PublicKeyToken 的值。
<?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="SampleRules.AvoidWaitForDelayRule" assembly="SampleRules, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b4deb9b383d021b0" enabled="true"/> </extensions>在 [檔案] 功能表上,按一下 [儲存]。
接下來,您會將組件資訊和 XML 檔複製到 Extensions 目錄。 當 Visual Studio 啟動時,它會識別 Microsoft Visual Studio 10.0\VSTSDB\Extensions 目錄和子目錄中的任何擴充,然後註冊這些擴充以在工作階段中使用。
若要將組件資訊和 XML 檔複製到 Extensions 目錄
在 Microsoft Visual Studio 10.0\VSTSDB\Extensions\ 目錄中建立名為 CustomRules 的新資料夾。
將 SampleRules.dll 組件檔從 Projects\SampleRules\SampleRules\bin\Debug\ 目錄複製到您剛才建立的 Microsoft Visual Studio 10.0\VSTSDB\Extensions\CustomRules 目錄中。
將 SampleRules.Extensions.xml 組件檔從 Projects\SampleRules\SampleRules\ 目錄複製到您剛才建立的 Microsoft Visual Studio 10.0\VSTSDB\Extensions\CustomRules 目錄中。
注意事項最佳做法是將擴充組件置於 Microsoft Visual Studio 10.0\VSTSDB\Extensions 目錄中的資料夾。 這有助您識別哪些擴充功能已包含在產品中,哪些是自訂建立的。 也建議使用資料夾,將擴充功能組織到特定類別。
接下來,您將啟動新的 Visual Studio 工作階段並建立資料庫專案。
若要啟動新的 Visual Studio 工作階段並建立資料庫專案
啟動第二個 Visual Studio 工作階段。
按一下 [檔案] 功能表上的 [新增],然後按一下 [專案]。
在 [新增專案] 對話方塊的 [已安裝的範本] 清單中,展開 [資料庫專案] 節點,然後按一下 [SQL Server]。
選取詳細資料窗格中的 [SQL Server 2008 資料庫專案]。
在 [名稱] 文字方塊中輸入 SampleRulesDB,然後按一下 [確定]。
最後,您將會看到 SQL Server 專案中顯示了新規則。
若要檢視新的 AvoidWaitForRule 程式碼分析規則
在 [方案總管] 中,選取 SampleRulesDB 專案。
在 [專案] 功能表上,按一下 [屬性]。
SampleRulesDB 屬性頁隨即顯示。
按一下 [程式碼分析]。
您應會看到名為 CategorySamples 的新類別。
展開 CategorySamples。
您應會看到 [SR1004: 避免在預存程序、觸發程序和函式中使用 WAITFOR DELAY 陳述式]。