Compartir a través de


Creación de un ensamblado de regla de análisis de código estático personalizado para SQL Server

En este tutorial se muestran los pasos utilizados para crear una regla de Code Analysis de SQL Server. La regla creada en este tutorial se utiliza para evitar las instrucciones WAITFOR DELAY en procedimientos almacenados, desencadenadores y funciones.

En este tutorial, crearás una regla personalizada de análisis de código estático de Transact-SQL mediante el uso de los siguientes procesos:

  1. Cree una biblioteca de clases, habilite la firma para el proyecto y agregue las referencias necesarias.

  2. Crea una clase de regla personalizada de C#.

  3. Crea dos clases del asistente de C#.

  4. Copie el archivo DLL resultante que ha creado en el directorio Extensions para instalarlo.

  5. Comprueba que la nueva regla de análisis de código está en su lugar.

Requisitos previos

Necesitará los componentes siguientes para completar este tutorial:

  • Una versión de Visual Studio instalada que incluya SQL Server Data Tools y admita el desarrollo de C# o Visual Basic .NET Framework.

  • Un proyecto de SQL que contenga objetos de SQL Server.

  • Una instancia de SQL Server a la que pueda implementar un proyecto de base de datos.

Este tutorial está destinado a usuarios que ya están familiarizados con las características de SQL Server de SQL Server Data Tools. Debes estar familiarizado con los conceptos de Visual Studio, por ejemplo, cómo crear una biblioteca de clases, agregar paquetes NuGet o cómo utilizar el editor de código para agregar código a una clase.

Creación de una regla de análisis de código personalizada para SQL Server

En primer lugar, cree una biblioteca de clases. Para crear un proyecto de biblioteca de clases:

  1. Crea un proyecto de biblioteca de clases de C# (.NET Framework) o Visual Basic (.NET Framework) denominado SampleRules.

  2. Cambie el nombre del archivo Class1.cs a AvoidWaitForDelayRule.cs.

  3. En el Explorador de soluciones, haz clic con el botón derecho en el nodo de proyecto y, a continuación, selecciona Agregar; después, selecciona Referencia.

  4. Selecciona System.ComponentModel.Composition en la pestaña Ensamblados\Marcos.

  5. En el Explorador de soluciones, haz clic con el botón derecho en el nodo del proyecto y, después, selecciona Administrar paquetes NuGet. Busca e instala el paquete de NuGet Microsoft.SqlServer.DacFx. La versión seleccionada debe ser 162.x.x (por ejemplo, 162.2.111) con Visual Studio 2022.

A continuación, agrega las clases auxiliares que utilizará la regla.

Creación de las clases auxiliares para la regla de análisis de código personalizada

Antes de crear la clase para la regla en sí, deberás agregar al proyecto una clase de visitante y una clase de atributos. Estas clases pueden ser útiles para crear reglas personalizadas adicionales.

Definición de la clase WaitForDelayVisitor

La primera clase que debes definir es la clase WaitForDelayVisitor, que se deriva de TSqlConcreteFragmentVisitor. Esta clase proporciona acceso a las instrucciones WAITFOR DELAY en el modelo. Las clases de visitante hacen uso de la API ScriptDom, proporcionada por SQL Server. En esta API, el código Transact-SQL se representa como un árbol de sintaxis abstracta (AST) y las clases de visitante pueden ser útiles para buscar objetos de sintaxis específica, como instrucciones WAITFOR DELAY. Puede ser difícil encontrar estas instrucciones utilizando el modelo de objetos, ya que no están asociadas a una propiedad de objeto o relación específicas, pero puedes encontrarlas mediante el patrón de visitante y la API ScriptDom.

  1. En el Explorador de soluciones, seleccione el proyecto SampleRules.

  2. En el menú Proyecto, seleccione Agregar clase. Aparecerá el cuadro de diálogo Agregar nuevo elemento .

  3. En el cuadro de texto Nombre, escribe WaitForDelayVisitor.cs y, después, selecciona Agregar. El archivoWaitForDelayVisitor.cs se agrega al proyecto en el Explorador de soluciones.

  4. Abre el archivoWaitForDelayVisitor.cs y actualiza el contenido para que coincida con el código siguiente:

    using System.Collections.Generic;
    using Microsoft.SqlServer.TransactSql.ScriptDom;
    namespace SampleRules {
        class WaitForDelayVistor {}
    }
    
  5. En la declaración de clase, cambia el modificador de acceso a internal y deriva la clase de TSqlConcreteFragmentVisitor:

    internal class WaitForDelayVisitor : TSqlConcreteFragmentVisitor {}
    
  6. Agregue el código siguiente para definir la variable de miembro de lista:

    public IList<WaitForStatement> WaitForDelayStatements { get; private set; }
    
  7. Defina el constructor de clase agregando el código siguiente:

    public WaitForDelayVisitor() {
       WaitForDelayStatements = new List<WaitForStatement>();
    }
    
  8. Anula el método ExplicitVisit al agregar el código siguiente:

    public override void ExplicitVisit(WaitForStatement node) {
       // We are only interested in WAITFOR DELAY occurrences
       if (node.WaitForOption == WaitForOption.Delay)
          WaitForDelayStatements.Add(node);
    }
    

    Este método pasa por las instrucciones WAITFOR del modelo y agrega las que tienen la opción DELAY especificada a la lista de instrucciones WAITFOR DELAY. La clase clave a la que se hace referencia es WaitForStatement.

  9. En el menú Archivo, seleccione Guardar.

Definición de la clase LocalizedExportCodeAnalysisRuleAttribute

La segunda clase es LocalizedExportCodeAnalysisRuleAttribute.cs. Se trata de una extensión de la versión integrada Microsoft.SqlServer.Dac.CodeAnalysis.ExportCodeAnalysisRuleAttribute proporcionada por el marco de trabajo y admite la lectura de DisplayName y Description usadas por la regla desde un archivo de recursos. Esta clase es útil si desea que las reglas puedan utilizarse en varios idiomas.

  1. En el Explorador de soluciones, seleccione el proyecto SampleRules.

  2. En el menú Proyecto, seleccione Agregar clase. Aparecerá el cuadro de diálogo Agregar nuevo elemento .

  3. En el cuadro de texto Nombre, escribe LocalizedExportCodeAnalysisRuleAttribute.cs y, después, selecciona Agregar. El archivo se agrega al proyecto en el Explorador de soluciones.

  4. Abre el archivo y actualiza el contenido para que coincida con el código siguiente:

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

Agregar un archivo de recursos y tres cadenas de recursos

A continuación, agrega un archivo de recursos en el que se defina el nombre de regla, su descripción y la categoría en la que aparecerá la regla en la interfaz de configuración de reglas.

  1. En el Explorador de soluciones, seleccione el proyecto SampleRules.

  2. En el menú Proyecto, selecciona Agregar y, a continuación, Nuevo elemento. Aparecerá el cuadro de diálogo Agregar nuevo elemento.

  3. En la lista de Plantillas instaladas, selecciona General.

  4. En el panel de detalles, selecciona Archivo de recursos.

  5. En Nombre, escriba RuleResources.resx. Aparece el editor de recursos, sin recursos definidos.

  6. Defina cuatro cadenas de recursos, como sigue:

    Nombre Valor
    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}.
  7. En el menú Archivo, selecciona Guardar RuleResources.resx.

Definición de la clase SampleConstants

A continuación, defina una clase que haga referencia a los recursos del archivo de recursos que Visual Studio utiliza para mostrar información acerca de la regla en la interfaz de usuario.

  1. En el Explorador de soluciones, seleccione el proyecto SampleRules.

  2. En el menú Proyecto, selecciona Agregar y, a continuación, Clase. Aparecerá el cuadro de diálogo Agregar nuevo elemento.

  3. En el cuadro de texto Nombre, escribe SampleRuleConstants.cs y selecciona el botón Agregar. El archivo SampleRuleConstants.cs se agrega al proyecto en el Explorador de soluciones.

  4. Abre el archivo SampleRuleConstants.cs y agrega lo siguiente al archivo usando instrucciones:

    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";
        }
    }
    
  5. Seleccione Archivo>Guardar.

Creación de la clase de regla de análisis de código personalizada

Después de agregar las clases del asistente que la regla de análisis de código personalizada utilizará, deberás crear una clase de reglas personalizada y asignarle el nombre AvoidWaitForDelayRule. La regla personalizada AvoidWaitForDelayRule se utilizará para ayudar a los desarrolladores de base de datos a evitar las instrucciones WAITFOR DELAY en los procedimientos, desencadenadores y funciones almacenados.

Creación de la clase AvoidWaitForDelayRule

  1. En el Explorador de soluciones, seleccione el proyecto SampleRules.

  2. En el menú Proyecto, selecciona Agregar y, a continuación, Clase. Aparecerá el cuadro de diálogo Agregar nuevo elemento.

  3. En el cuadro Nombre, escribe AvoidWaitForDelayRule.cs y, después, selecciona Agregar. El archivo AvoidWaitForDelayRule.cs se agrega al proyecto en el Explorador de soluciones.

  4. Abre el archivo AvoidWaitForDelayRule.cs y agrega lo siguiente al archivo usando instrucciones:

    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 {}
    }
    
  5. En la declaración de clase AvoidWaitForDelayRule, cambia el modificador de acceso a public:

    /// <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
    
  6. Deriva la clase AvoidWaitForDelayRule de la clase base Microsoft.SqlServer.Dac.CodeAnalysis.SqlCodeAnalysisRule:

    public sealed class AvoidWaitForDelayRule : SqlCodeAnalysisRule
    
  7. Agregue LocalizedExportCodeAnalysisRuleAttribute a la clase.

    LocalizedExportCodeAnalysisRuleAttribute permite al servicio de análisis de código detectar reglas de análisis de código personalizado. Solo las clases marcadas con un ExportCodeAnalysisRuleAttribute (o un atributo que hereda de este) pueden utilizarse en el análisis de código.

    LocalizedExportCodeAnalysisRuleAttribute proporciona algunos metadatos necesarios usados por el servicio. Esto incluye un identificador único para esta regla, un nombre que se mostrará en la interfaz de usuario de Visual Studio y una Description que la regla puede utilizar para identificar los problemas.

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

    La propiedad RuleScope debe ser Microsoft.SqlServer.Dac.CodeAnalysis.SqlRuleScope.Element, ya que esta regla analiza elementos específicos. La regla se llamará una vez para cada elemento coincidente del modelo. Si deseas analizar un modelo completo, Microsoft.SqlServer.Dac.CodeAnalysis.SqlRuleScope.Model se puede usar en su lugar.

  8. Agrega un constructor que configure Microsoft.SqlServer.Dac.CodeAnalysis.SqlAnalysisRule.SupportedElementTypes. Esto es necesario para las reglas de ámbito de elemento. Define los tipos de elemento a los que se aplicará esta regla. En este caso, la regla se aplicará a los procedimientos, desencadenadores y funciones almacenados. La clase Microsoft.SqlServer.Dac.Model.ModelSchema enumera todos los tipos de elementos disponibles que se pueden analizar.

    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
       };
    }
    
  9. Agrega una invalidación para el método Microsoft.SqlServer.Dac.CodeAnalysis.SqlAnalysisRule.Analyze (Microsoft.SqlServer.Dac.CodeAnalysis.SqlRuleExecutionContext), que usa Microsoft.SqlServer.Dac.CodeAnalysis.SqlRuleExecutionContext como parámetros de entrada. Este método devuelve una lista de posibles problemas.

    El método obtiene Microsoft.SqlServer.Dac.Model.TSqlModel, Microsoft.SqlServer.Dac.Model.TSqlObject y TSqlFragment del parámetro de contexto. A continuación se utiliza la clase WaitForDelayVisitor para obtener una lista de todas las instrucciones WAITFOR DELAY del modelo.

    Para cada WaitForStatement de esa lista, se crea un 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);
    }
    
  10. Seleccione Archivo>Guardar.

Compilación de la biblioteca de clases

  1. En el menú Proyecto, haz clic en Propiedades de SampleRules.

  2. Selecciona la pestaña Firma.

  3. Seleciona la casilla Firmar el ensamblado.

  4. En la lista Elija un archivo de clave de nombre seguro, selecciona <Nuevo>.

  5. En el cuadro de diálogo Crear clave de nombre seguro, en el Nombre del archivo clave, escribe MyRefKey.

  6. (opcional) Puede especificar una contraseña para el archivo de clave de nombre seguro.

  7. Seleccione Aceptar.

  8. En el menú Archivo, seleccione Guardar todo.

  9. En el menú Compilar, seleccione Compilar solución.

Instalación de una regla de análisis de código estático

A continuación, debes instalar el ensamblado para que se cargue cuando compile e implemente proyectos de SQL Server.

Para instalar una regla, debes copiar el ensamblado y el archivo .pdb asociado en la carpeta Extensions.

Instalar el ensamblado SampleRules

Después, copia la información del ensamblado en el directorio Extensions. Cuando se inicie Visual Studio, este identificará todas las extensiones en el directorio <Visual Studio Install Dir>\Common7\IDE\Extensions\Microsoft\SQLDB\DAC\Extensions y sus subdirectorios y las dejará disponibles para su uso.

Para Visual Studio 2022, <Visual Studio Install Dir> normalmente es C:\Program Files\Microsoft Visual Studio\2022\Enterprise. Reemplaza Enterprise por Professional o Community en función de la edición de Visual Studio instalada.

Copia el archivo de ensamblado SampleRules.dll del directorio de salida en el directorio <Visual Studio Install Dir>\Common7\IDE\Extensions\Microsoft\SQLDB\DAC\Extensions. De manera predeterminada, la ruta de acceso del archivo compilado .dll es YourSolutionPath\YourProjectPath\bin\Debug o YourSolutionPath\YourProjectPath\bin\Release.

Nota:

Es posible que tengas que crear el directorio Extensions.

La regla deberá entonces instalarse y aparecer al reiniciar Visual Studio. Después, iniciarás una nueva sesión de Visual Studio y crearás un proyecto de base de datos.

Inicio de una nueva sesión de Visual Studio y creación de un proyecto de base de datos

  1. Inicie una segunda sesión de Visual Studio.

  2. Seleccione Archivo>Nuevo>Proyecto.

  3. En el cuadro de diálogo Nuevo proyecto, busca y selecciona Proyecto de base de datos de SQL Server.

  4. En el cuadro Nombre, escribe SampleRulesDB y selecciona Aceptar.

Por último, verás la nueva regla en el proyecto SQL Server. Para ver la nueva regla AvoidWaitForRule de Code Analysis:

  1. En el Explorador de soluciones, seleccione el proyecto SampleRulesDB.

  2. En el menú Proyecto, seleccione Propiedades. Se muestra la página Propiedades de SampleRulesDB.

  3. Selecciona Análisis de código. Deberías ver una nueva categoría denominada RuleSamples.CategorySamples.

  4. Expanda RuleSamples.CategorySamples. Deberías ver SR1004: Avoid WAITFOR DELAY statement in stored procedures, triggers, and functions.