编写适用于 SQL Server 的自定义静态代码分析规则程序集
此演练演示创建 SQL Server 代码分析规则的步骤。 在此演练中创建的规则用于避免存储过程、触发器和函数中的 WAITFOR DELAY
语句。
此演练使用以下过程创建 Transact-SQL 静态代码分析的自定义规则:
创建类库、启用该项目的签名并添加必要的引用。
创建 C# 自定义规则类。
创建两个帮助程序 C# 类。
为了安装你创建的已生成 DLL,请将其复制到 Extensions 目录中。
请验证新 Code Analysis 规则是否已到位。
先决条件
你需要满足以下条件才能完成本演练:
已安装包含 SQL Server Data Tools 且支持 C# 或 Visual Basic .NET Framework 开发的 Visual Studio 版本。
包含 SQL Server 对象的 SQL Server 项目。
可以向其部署数据库项目的 SQL Server 实例。
本演练面向已熟悉 SQL Server Data Tools 的 SQL Server 功能的用户。 你应熟悉相关 Visual Studio 概念,例如,如何创建类库、添加 NuGet 包以及如何使用代码编辑器向类添加代码。
创建 SQL Server 的自定义 Code Analysis 规则
首先创建类库。 若要创建类库项目:
创建名为
SampleRules
的 C# (.NET Framework) 或 Visual Basic (.NET Framework) 类库项目。将文件
Class1.cs
重命名为AvoidWaitForDelayRule.cs
。在解决方案资源管理器中,右键单击项目节点,然后依次选择“添加”、“引用”。
在“程序集\框架”选项卡上选择
System.ComponentModel.Composition
。在解决方案资源管理器中,右键单击项目节点,然后选择“管理 NuGet 包”。 找到并安装
Microsoft.SqlServer.DacFx
NuGet 包。 所选版本必须为具有 Visual Studio 2022 的162.x.x
(例如162.2.111
)。
然后添加将由该规则使用的支持类。
创建自定义 Code Analysis 规则支持类
在创建规则本身的类之前,首先需要为项目添加访问者类和特性类。 这些类对于创建其他自定义规则可能非常实用。
定义 WaitForDelayVisitor 类
第一种必须定义的类是从 TSqlConcreteFragmentVisitor 派生的 WaitForDelayVisitor
类。 此类提供了对模型中 WAITFOR DELAY
语句的访问权限。 访问者类使用 SQL Server 提供的 ScriptDom API。 在此 API 中,Transact-SQL 代码表示为抽象语法树 (AST),如果希望查找特定语法对象(例如 WAITFOR DELAY
语句),访问者类可能非常实用。 使用对象模型可能难以找到这些语句,因为它们没有关联特定的对象属性或关系,而使用访问者模式和 ScriptDom API 就能够找到它们。
在“解决方案资源管理器”中,选择
SampleRules
项目。在“项目”菜单上,选择“添加类”。 此时会显示“添加新项”对话框。
在“名称”文本框中键入
WaitForDelayVisitor.cs
,然后选择“添加”按钮。WaitForDelayVisitor.cs
文件将被添加到“解决方案资源管理器”中的项目。打开
WaitForDelayVisitor.cs
文件并更新内容,以与以下代码相匹配:using System.Collections.Generic; using Microsoft.SqlServer.TransactSql.ScriptDom; namespace SampleRules { class WaitForDelayVistor {} }
在类声明中,将访问修饰符更改为内部并从
TSqlConcreteFragmentVisitor
派生类:internal class WaitForDelayVisitor : TSqlConcreteFragmentVisitor {}
添加以下代码以定义列表成员变量:
public IList<WaitForStatement> WaitForDelayStatements { get; private set; }
通过添加以下代码,定义类构造函数:
public WaitForDelayVisitor() { WaitForDelayStatements = new List<WaitForStatement>(); }
通过添加以下代码,重写
ExplicitVisit
方法:public override void ExplicitVisit(WaitForStatement node) { // We are only interested in WAITFOR DELAY occurrences if (node.WaitForOption == WaitForOption.Delay) WaitForDelayStatements.Add(node); }
此方法会访问模型中的
WAITFOR
语句,并且将指定了DELAY
选项的语句添加到WAITFOR DELAY
语句列表中。 引用的关键类为 WaitForStatement。在“文件”菜单上,选择“保存”。
定义 LocalizedExportCodeAnalysisRuleAttribute 类
第二个类为 LocalizedExportCodeAnalysisRuleAttribute.cs
。 这是框架提供的内置 Microsoft.SqlServer.Dac.CodeAnalysis.ExportCodeAnalysisRuleAttribute
的扩展,支持从资源文件中读取规则使用的 DisplayName
和 Description
。 如果之前计划在多种语言中使用规则,此类会很有用。
在“解决方案资源管理器”中,选择
SampleRules
项目。在“项目”菜单上,选择“添加类”。 此时会显示“添加新项”对话框。
在“名称”文本框中键入
LocalizedExportCodeAnalysisRuleAttribute.cs
,然后选择“添加”按钮。 该文件将添加到“解决方案资源管理器”中的项目。打开 文件并更新内容,以与以下代码相匹配:
using Microsoft.SqlServer.Dac.CodeAnalysis; using System; using System.Globalization; using System.Reflection; using System.Resources; namespace SampleRules { internal class LocalizedExportCodeAnalysisRuleAttribute : ExportCodeAnalysisRuleAttribute { private readonly string _resourceBaseName; private readonly string _displayNameResourceId; private readonly string _descriptionResourceId; private ResourceManager _resourceManager; private string _displayName; private string _descriptionValue; /// <summary> /// Creates the attribute, with the specified rule ID, the fully qualified /// name of the resource file that will be used for looking up display name /// and description, and the Ids of those resources inside the resource file. /// </summary> public LocalizedExportCodeAnalysisRuleAttribute( string id, string resourceBaseName, string displayNameResourceId, string descriptionResourceId) : base(id, null) { _resourceBaseName = resourceBaseName; _displayNameResourceId = displayNameResourceId; _descriptionResourceId = descriptionResourceId; } /// <summary> /// Rules in a different assembly would need to overwrite this /// </summary> /// <returns></returns> protected virtual Assembly GetAssembly() { return GetType().Assembly; } private void EnsureResourceManagerInitialized() { var resourceAssembly = GetAssembly(); try { _resourceManager = new ResourceManager(_resourceBaseName, resourceAssembly); } catch (Exception ex) { var msg = String.Format(CultureInfo.CurrentCulture, RuleResources.CannotCreateResourceManager, _resourceBaseName, resourceAssembly); throw new RuleException(msg, ex); } } private string GetResourceString(string resourceId) { EnsureResourceManagerInitialized(); return _resourceManager.GetString(resourceId, CultureInfo.CurrentUICulture); } /// <summary> /// Overrides the standard DisplayName and looks up its value inside a resources file /// </summary> public override string DisplayName { get { if (_displayName == null) { _displayName = GetResourceString(_displayNameResourceId); } return _displayName; } } /// <summary> /// Overrides the standard Description and looks up its value inside a resources file /// </summary> public override string Description { get { if (_descriptionValue == null) { _descriptionValue = GetResourceString(_descriptionResourceId); } return _descriptionValue; } } } }
添加一个资源文件和三个资源字符串
接下来,添加一个资源文件,该文件定义规则名称、规则描述以及类别(规则将在规则配置界面的该类别中显示)。
在“解决方案资源管理器”中,选择
SampleRules
项目。在“项目”菜单上,选择“添加”,然后选择“新项”。 “添加新项”对话框随即出现。
在“已安装的模板”列表中,选择“常规”。
在详细内容窗格中,选择“资源文件”。
在 “名称”中,键入
RuleResources.resx
。 资源编辑器随即出现,其中未定义任何资源。定义 4 个资源字符串,如下所示:
名称 值 AvoidWaitForDelay_ProblemDescription
WAITFOR DELAY statement was found in {0}.
AvoidWaitForDelay_RuleName
Avoid using WaitFor Delay statements in stored procedures, functions and triggers.
CategorySamples
SamplesCategory
CannotCreateResourceManager
Can't create ResourceManager for {0} from {1}.
在“文件”菜单上,选择“保存RuleResources.resx”。
定义 SampleConstants 类
接下来,定义某个类(该类引用资源文件中由 Visual Studio 使用的资源),以在用户界面中显示有关规则的信息。
在“解决方案资源管理器”中,选择
SampleRules
项目。在“项目”菜单上,选择“添加”,然后选择“类”。 “添加新项”对话框随即出现。
在“名称”文本框中,键入“
SampleRuleConstants.cs
”,然后选择“添加”按钮。SampleRuleConstants.cs
文件将被添加到“解决方案资源管理器”中的项目。打开
SampleRuleConstants.cs
文件并将以下 using 语句添加到该文件:namespace SampleRules { internal static class RuleConstants { /// <summary> /// The name of the resources file to use when looking up rule resources /// </summary> public const string ResourceBaseName = "Public.Dac.Samples.Rules.RuleResources"; /// <summary> /// Lookup name inside the resources file for the select asterisk rule name /// </summary> public const string AvoidWaitForDelay_RuleName = "AvoidWaitForDelay_RuleName"; /// <summary> /// Lookup ID inside the resources file for the select asterisk description /// </summary> public const string AvoidWaitForDelay_ProblemDescription = "AvoidWaitForDelay_ProblemDescription"; /// <summary> /// The design category (should not be localized) /// </summary> public const string CategoryDesign = "Design"; /// <summary> /// The performance category (should not be localized) /// </summary> public const string CategoryPerformance = "Design"; } }
选择“文件”>“保存”。
创建自定义 Code Analysis 规则类
添加自定义 Code Analysis 规则将使用的帮助程序类后,创建一个自定义规则类并将其命名为 AvoidWaitForDelayRule
。 AvoidWaitForDelayRule
自定义规则将用于帮助数据库开发人员避免在存储过程、触发器和函数中使用 WAITFOR DELAY
语句。
创建 AvoidWaitForDelayRule 类
在“解决方案资源管理器”中,选择
SampleRules
项目。在“项目”菜单上,选择“添加”,然后选择“类”。 “添加新项”对话框随即出现。
在“名称”文本框中,键入
AvoidWaitForDelayRule.cs
,然后选择“添加”。AvoidWaitForDelayRule.cs
文件将被添加到“解决方案资源管理器”中的项目。打开
AvoidWaitForDelayRule.cs
文件并将以下 using 语句添加到该文件:using Microsoft.SqlServer.Dac.CodeAnalysis; using Microsoft.SqlServer.Dac.Model; using Microsoft.SqlServer.TransactSql.ScriptDom; using System; using System.Collections.Generic; using System.Globalization; namespace SampleRules { class AvoidWaitForDelayRule {} }
在
AvoidWaitForDelayRule
类声明中,将访问修饰符更改为公共:/// <summary> /// This is a rule that returns a warning message /// whenever there is a WAITFOR DELAY statement appears inside a subroutine body. /// This rule only applies to stored procedures, functions and triggers. /// </summary> public sealed class AvoidWaitForDelayRule
AvoidWaitForDelayRule
类派生自Microsoft.SqlServer.Dac.CodeAnalysis.SqlCodeAnalysisRule
基类:public sealed class AvoidWaitForDelayRule : SqlCodeAnalysisRule
将
LocalizedExportCodeAnalysisRuleAttribute
添加到类中。LocalizedExportCodeAnalysisRuleAttribute
允许 Code Analysis 服务发现自定义 Code Analysis 规则。 只有使用ExportCodeAnalysisRuleAttribute
标记的类(或派生自此类的属性)可以用于代码分析。LocalizedExportCodeAnalysisRuleAttribute
提供服务使用的一些必需元数据。 包括此规则的唯一 ID、将在 Visual Studio 用户界面显示的显示名称以及标识问题时可由规则使用的Description
。[LocalizedExportCodeAnalysisRule(AvoidWaitForDelayRule.RuleId, RuleConstants.ResourceBaseName, RuleConstants.AvoidWaitForDelay_RuleName, RuleConstants.AvoidWaitForDelay_ProblemDescription Category = RuleConstants.CategoryPerformance, RuleScope = SqlRuleScope.Element)] public sealed class AvoidWaitForDelayRule : SqlCodeAnalysisRule { /// <summary> /// The Rule ID should resemble a fully-qualified class name. In the Visual Studio UI /// rules are grouped by "Namespace + Category", and each rule is shown using "Short ID: DisplayName". /// For this rule, that means the grouping will be "Public.Dac.Samples.Performance", with the rule /// shown as "SR1004: Avoid using WaitFor Delay statements in stored procedures, functions and triggers." /// </summary> public const string RuleId = "RuleSamples.SR1004"; }
当此规则分析特定元素时,RuleScope 属性应为
Microsoft.SqlServer.Dac.CodeAnalysis.SqlRuleScope.Element
。 该规则为模型中的每个匹配元素调用一次。 如果想要分析整个模型,则可以改用Microsoft.SqlServer.Dac.CodeAnalysis.SqlRuleScope.Model
。添加设置
Microsoft.SqlServer.Dac.CodeAnalysis.SqlAnalysisRule.SupportedElementTypes
的构造函数。 这是元素范围内的规则所必需的。 它定义了应用此规则的元素的类型。 在这种情况下,此规则会应用于存储过程、触发器和函数。Microsoft.SqlServer.Dac.Model.ModelSchema
类列出了可以分析的所有可用元素类型。public AvoidWaitForDelayRule() { // This rule supports Procedures, Functions and Triggers. Only those objects will be passed to the Analyze method SupportedElementTypes = new[] { // Note: can use the ModelSchema definitions, or access the TypeClass for any of these types ModelSchema.ExtendedProcedure, ModelSchema.Procedure, ModelSchema.TableValuedFunction, ModelSchema.ScalarFunction, ModelSchema.DatabaseDdlTrigger, ModelSchema.DmlTrigger, ModelSchema.ServerDdlTrigger }; }
添加
Microsoft.SqlServer.Dac.CodeAnalysis.SqlAnalysisRule.Analyze
(Microsoft.SqlServer.Dac.CodeAnalysis.SqlRuleExecutionContext)
方法的替代,该方法将Microsoft.SqlServer.Dac.CodeAnalysis.SqlRuleExecutionContext
用作输入参数。 此方法返回潜在问题列表。该方法从上下文参数中获取
Microsoft.SqlServer.Dac.Model.TSqlModel
、Microsoft.SqlServer.Dac.Model.TSqlObject
和 TSqlFragment。 然后使用WaitForDelayVisitor
类获取包含模型中所有WAITFOR DELAY
语句的列表。对于该列表中的每个 WaitForStatement,将创建
Microsoft.SqlServer.Dac.CodeAnalysis.SqlRuleProblem
。/// <summary> /// For element-scoped rules the Analyze method is executed once for every matching /// object in the model. /// </summary> /// <param name="ruleExecutionContext">The context object contains the TSqlObject being /// analyzed, a TSqlFragment /// that's the AST representation of the object, the current rule's descriptor, and a /// reference to the model being /// analyzed. /// </param> /// <returns>A list of problems should be returned. These will be displayed in the Visual /// Studio error list</returns> public override IList<SqlRuleProblem> Analyze( SqlRuleExecutionContext ruleExecutionContext) { IList<SqlRuleProblem> problems = new List<SqlRuleProblem>(); TSqlObject modelElement = ruleExecutionContext.ModelElement; // this rule does not apply to inline table-valued function // we simply do not return any problem in that case. if (IsInlineTableValuedFunction(modelElement)) { return problems; } string elementName = GetElementName(ruleExecutionContext, modelElement); // The rule execution context has all the objects we'll need, including the // fragment representing the object, // and a descriptor that lets us access rule metadata TSqlFragment fragment = ruleExecutionContext.ScriptFragment; RuleDescriptor ruleDescriptor = ruleExecutionContext.RuleDescriptor; // To process the fragment and identify WAITFOR DELAY statements we will use a // visitor WaitForDelayVisitor visitor = new WaitForDelayVisitor(); fragment.Accept(visitor); IList<WaitForStatement> waitforDelayStatements = visitor.WaitForDelayStatements; // Create problems for each WAITFOR DELAY statement found // When creating a rule problem, always include the TSqlObject being analyzed. This // is used to determine // the name of the source this problem was found in and a best guess as to the // line/column the problem was found at. // // In addition if you have a specific TSqlFragment that is related to the problem //also include this // since the most accurate source position information (start line and column) will // be read from the fragment foreach (WaitForStatement waitForStatement in waitforDelayStatements) { SqlRuleProblem problem = new SqlRuleProblem( String.Format(CultureInfo.CurrentCulture, ruleDescriptor.DisplayDescription, elementName), modelElement, waitForStatement); problems.Add(problem); } return problems; } private static string GetElementName( SqlRuleExecutionContext ruleExecutionContext, TSqlObject modelElement) { // Get the element name using the built in DisplayServices. This provides a number of // useful formatting options to // make a name user-readable var displayServices = ruleExecutionContext.SchemaModel.DisplayServices; string elementName = displayServices.GetElementName( modelElement, ElementNameStyle.EscapedFullyQualifiedName); return elementName; } private static bool IsInlineTableValuedFunction(TSqlObject modelElement) { return TableValuedFunction.TypeClass.Equals(modelElement.ObjectType) && FunctionType.InlineTableValuedFunction == modelElement.GetMetadata<FunctionType>(TableValuedFunction.FunctionType); }
选择“文件”>“保存”。
生成类库
在“项目”菜单上,选择“SampleRules 属性”。
选择“签名”选项卡。
选择“为程序集签名”。
在“选择强名称密钥文件”中,选择“新建”<>。
在“创建强名称密钥”对话框的“密钥文件名称”中,键入“
MyRefKey
”。(可选)可以为强名称密钥文件指定密码。
选择“确定”。
在“文件”菜单上,单击“全部保存”。
在“生成”菜单上,选择“生成解决方案” 。
安装静态 Code Analysis 规则
然后必须安装程序集,以便在生成和部署 SQL Server 项目时加载该程序集。
要安装规则,必须将程序集和关联的 .pdb
文件复制到 Extensions 文件夹。
安装 SampleRules 程序集
然后将程序集信息复制到 Extensions 目录中。 当 Visual Studio 启动后,它会标识 <Visual Studio Install Dir>\Common7\IDE\Extensions\Microsoft\SQLDB\DAC\Extensions
目录和子目录中的任何扩展,并使其可供使用。
对于 Visual Studio 2022,<Visual Studio Install Dir>
通常为 C:\Program Files\Microsoft Visual Studio\2022\Enterprise
。 将 Enterprise
替换为 Professional
或 Community
,具体取决于安装的 Visual Studio 版本。
将 SampleRules.dll 程序集文件从输出目录复制到 <Visual Studio Install Dir>\Common7\IDE\Extensions\Microsoft\SQLDB\DAC\Extensions
该目录。 默认情况下,已编译的 .dll
文件的路径为 YourSolutionPath\YourProjectPath\bin\Debug
或 YourSolutionPath\YourProjectPath\bin\Release
。
注意
可能需要创建 Extensions
目录。
现在规则安装完成,重新启动 Visual Studio 即可显示。 然后启动 Visual Studio 的一个新会话并且创建一个数据库项目。
启动新 Visual Studio 会话并且创建数据库项目
启动 Visual Studio 的第二个会话。
选择“文件”>“新建”>“项目”。
在“新建项目”对话框中,找到并选择“SQL Server 数据库项目”。
在“名称”文本框中,键入
SampleRulesDB
,然后选择“确定”。
最后会看到在 SQL Server 项目中显示的新规则。 要查看新的 AvoidWaitForRule
Code Analysis 规则,请执行以下操作:
在“解决方案资源管理器”中,选择
SampleRulesDB
项目。在“项目”菜单上选择“属性”。 此时将显示
SampleRulesDB
属性页。选择“Code Analysis”。 应会看到名为
RuleSamples.CategorySamples
的新类别。展开
RuleSamples.CategorySamples
。 应会看到SR1004: Avoid WAITFOR DELAY statement in stored procedures, triggers, and functions
。
相关内容
反馈
https://aka.ms/ContentUserFeedback。
即将发布:在整个 2024 年,我们将逐步淘汰作为内容反馈机制的“GitHub 问题”,并将其取代为新的反馈系统。 有关详细信息,请参阅:提交和查看相关反馈