Hola, chicos,
¡Buen día!

En esta publicación, que será la número 100 del blog, me gustaría hablar sobre algo que me gusta particularmente de SQL Server, que es la creación de rutinas .NET dentro de la base de datos de SQL Server. Sí, estamos hablando del CLR (Common Language Runtime).

Introducción

Haga clic aquí para ver
Presente en la base de datos SQL Server desde la versión 2005, el CLR es el corazón de los lenguajes de la plataforma Microsoft .NET Framework. El código que se ejecuta dentro de CLR se conoce como código administrado. CLR proporciona varias funciones y servicios necesarios para la ejecución del programa, incluida la compilación justo a tiempo (JIT), la asignación y administración de memoria, la aplicación de seguridad de tipos, el manejo de excepciones, la administración de subprocesos y la seguridad.

En otras palabras, el CLR permite crear rutinas (procedimientos almacenados, funciones, disparadores, etc.) escritas en C#, F# y VB.NET, compilarlas y ejecutarlas en la base de datos de forma nativa, ampliando las capacidades del DBMS, ya que es posible crear multitud de cosas que no serían posibles usando solo Transact-SQL, como manipulación de archivos, carga y descarga de archivos vía FTP, funciones de agregación, integración con Webservices y mucho más.

Ventajas y desventajas

Haga clic aquí para ver
Al igual que cualquier tecnología y herramienta, siempre hay fortalezas y debilidades en las que debes o no usar el CLR. Voy a enumerar algunos:

Ventajas de SQL CLR

  • Posibilidad de crear nuevos recursos, lo que no sería posible usando solo T-SQL
  • Posibilidad de trabajar con expresiones regulares (RegExp) en la base de datos.
  • Optimización del rendimiento: Una misma función escrita en C# en el CLR generalmente se ejecuta mucho más rápido que una función T-SQL, especialmente en casos de bucles y cálculos, ya que el compilador .NET está especializado para este tipo de operaciones, mientras que el compilador T-SQL es para trabajar con conjuntos. En mi trabajo, he visto varios casos en los que la misma función CLR se realizó 5 veces, 10 veces y algunos incluso 60 veces más rápido que la función T-SQL.
  • Integración con Webservices vía base de datos.
  • Seguridad: Una cosa interesante que me gusta de CLR es que podemos definir un usuario fijo para conectarse a la base de datos. De esta forma podremos liberar acceso para que ejecute SP’s y consulte vistas y tablas del sistema y cree funciones y SP’s en el CLR para ello. Cuando un analista necesita utilizar estos SP del sistema, simplemente libere el acceso al SP/Vista/Función del CLR, que tendrá acceso indirecto al objeto del sistema, sin necesidad de liberar el acceso al mismo en el objeto fuente o crear objetos en los bancos del sistema.
  • Herramientas de desarrollo: La herramienta utilizada para desarrollar rutinas CLR es Visual Studio. Management Studio es un IDE muy bueno para crear rutinas Transact-SQL, especialmente con SQL Prompt instalado, pero no se compara con el poderoso Visual Studio, especialmente con ReSharper. La programación es mucho más rápida y práctica.
  • Versionado del código fuente: Debido a que utilizamos Visual Studio, el código fuente puede ser fácilmente controlado y administrado por TFS (Team Foundation Server), dando control total sobre los códigos creados, a diferencia de los Procedimientos Almacenados en la base de datos, que no tienen controles como Merge, Diff, etc.
  • Reemplazo de xp_cmdshell: A pesar de estar deshabilitado por defecto, muchas personas y empresas terminan habilitando cmdshell en sus instancias, incluso en producción, ya que algunas operaciones no se pueden realizar usando solo Transact-SQL, como la manipulación de archivos, por ejemplo. Esta característica es un gran peligro ya que simplemente ejecuta cualquier comando que se le envía sin ningún filtro ni restricción. Para ello, recomiendo desactivar esta función y utilizar procedimientos CLR diseñados exclusivamente para cada propósito, ya sea copiar archivos o incluso cargar una instancia.
  • Reemplazo de OLE Automation: Una característica que todavía se usa ampliamente y también está deshabilitada de manera predeterminada, los procedimientos de OLE Automation son bibliotecas de C++ que le permiten usar las API de Windows para realizar diversas operaciones, como manipulación de archivos, etc. El gran problema es que al habilitar esta característica, cualquier usuario puede crear cualquier cosa con ella, combinado con el hecho de que los comandos no se administran y se ejecutan dentro del proceso de SQL Server. En caso de falla, la instancia se cierra, ya que el sistema operativo cierra automáticamente el proceso de SQL Server (!!!!)
  • Automatización: Con los procedimientos CLR puedes automatizar multitud de procesos del día a día, que antes solo se podían automatizar usando Integration Services, que es una gran herramienta, pero termina quedando un poco limitado en el mundo de las posibilidades de CLR, ya que en MSIS solo tienes los recursos que las herramientas ponen a tu disposición, mientras que en CLR puedes crear cualquier cosa que la plataforma .NET te permita. Además, el resultado del CLR son objetos de la base de datos, ya sean Productos Almacenados, Funciones, Triggers, etc., que pueden usarse libremente en otros SP's, Jobs y cualquier otro objeto de la base de datos, mientras que los Paquetes solo pueden ser ejecutados por la herramienta o por los Jobs. (Nota: CLR y MSIS son herramientas con objetivos diferentes, solo las comparé porque algunas tareas de Integration Services pueden ser reemplazadas fácilmente por CLR)
  • Conectividad: Posibilidad de utilizar conectores .NET Framework y acceder a otros DBMS y otras instancias con un enlace directo, sin necesidad de un LinkedServer, que ejecuta el comando de forma remota

Desventajas de SQL CLR

  • Necesidad de conocer SQL y lenguaje de programación (C#, F# o VB.net)
  • Poca documentación y gente con conocimientos sobre el tema.
  • Al publicar una nueva versión, los objetos se eliminan y se vuelven a crear, perdiendo permisos y haciendo que los objetos no estén disponibles durante la publicación.
  • Si está mal desarrollado e implementado, puede presentar riesgos para el DBMS.
  • Algunas funciones pueden requerir un alto volumen de CPU para su procesamiento
  • No hay parámetros opcionales para los procedimientos. Todo debe estar completo.

Habilitando el CLR en su instancia de SQL Server

Haga clic aquí para ver
De forma predeterminada, CLR está deshabilitado en instancias de SQL Server. Para habilitar esta característica, es muy simple, simplemente use el comando sp_configure:
sp_configure 'clr enabled'
GO
sp_configure 'clr enabled', 1
GO
RECONFIGURE
GO
sp_configure 'clr enabled'
GO

Y tendremos el siguiente resultado:

Habilitando CLR no SQL Server
Habilitar CLR en SQL Server

De lo contrario, encontrará este mensaje de error al intentar utilizar CLR:

Mensaje 6263, Nivel 16, Estado 1, Línea 2
La ejecución de código de usuario en .NET Framework está deshabilitada. Habilite la opción de configuración “clr habilitado”.

Creando su primer proyecto SQL CLR en Visual Studio

Haga clic aquí para ver
El primer paso para crear nuestro proyecto CLR es obviamente descargar e instalar Visual Studio compatible con su versión de SQL Server (recomiendo Edición comunitaria de Visual Studio 2015, ya que es compatible con SQL Server 2005 a 2014 y gratuito para desarrolladores).

Después de eso, abra Visual Studio y acceda al menú Archivo > Nuevo > Proyecto. Seleccione el tipo de proyecto SQL Server > Proyecto de base de datos de SQL Server

New CLR Project
Nuevo proyecto CLR

Y inicialmente debería verse así:

CLR novo projeto - Solution explorer
Nuevo proyecto CLR - Explorador de soluciones

Ahora agreguemos un nuevo proyecto de biblioteca de clases, que contendrá nuestros códigos C#. Para hacer esto, haga clic derecho en la Solución y seleccione la opción Agregar > Nuevo proyecto. Seleccione la categoría Visual C# > Windows > Biblioteca de clases

CLR New Class Library
Biblioteca de nuevas clases CLR

Una vez creado, generalmente creo directorios por tipo de objeto en la Biblioteca de clases para organizar mejor el código. Esto es recomendado pero opcional. Es posible que desee organizar su código por tema o según sus necesidades.

Es posible que se eliminen algunas referencias ya que no se utilizarán en los ejemplos. Haga clic derecho en "Referencias" en el proyecto CLR y seleccione la opción "Agregar referencia ...". En la pantalla que se abrirá, seleccione la categoría Proyectos > Soluciones y marque la casilla de verificación del proyecto Biblioteca de clases:

CLR Add Reference
CLR Agregar referencia

No olvide configurar el permiso del proyecto que se importó, como se muestra en la siguiente figura:

CLR Assembly Permission Set 2
Conjunto de permisos de ensamblaje CLR 2

El Explorador de soluciones debería verse similar a lo siguiente:

CLR novo projeto com Class Library - Solution explorer
Nuevo proyecto CLR con biblioteca de clases: explorador de soluciones

Creando un procedimiento almacenado sin retorno

  • Haga clic derecho en el directorio “Procedimientos” del proyecto Bibliotecas y seleccione la opción Agregar > Clase…. En la pantalla que se abre, escriba el nombre del archivo que se creará. Normalmente pongo el mismo nombre que el objeto que se creará en la base de datos. En este ejemplo, crearé el archivo stpCopia_Arquivo.cs
  • Copie y pegue el siguiente código:
    using System.IO;
    
    public partial class StoredProcedures
    {
        [Microsoft.SqlServer.Server.SqlProcedure]
        public static void stpCopia_Arquivo(string origem, string destino, bool sobrescrever)
        {
            File.Copy(origem, destino, sobrescrever);
        }
    }

Crear un procedimiento almacenado con retorno de una selección

  • Haga clic derecho en el directorio “Procedimientos” del proyecto Bibliotecas y seleccione la opción Agregar > Clase…. En la pantalla que se abre, escriba el nombre del archivo que se creará. Normalmente pongo el mismo nombre que el objeto que se creará en la base de datos. En este ejemplo, crearé el archivo stpImporta_Txt.cs
  • Copie y pegue el siguiente código:
    using System.Data;
    using System.Data.SqlTypes;
    using Microsoft.SqlServer.Server;
    using System.IO;
    
    public partial class StoredProcedures
    {
        [Microsoft.SqlServer.Server.SqlProcedure]
        public static void stpImporta_Txt(SqlString caminho)
        {
    
            if (caminho.IsNull) return;
    
            using (var sr = new StreamReader(caminho.Value))
            {
    
                // Define um novo objeto de metadados com 2 colunas
                var colunas = new SqlMetaData[2];
    
                // Define as colunas de retorno
                colunas[0] = new SqlMetaData("Nr_Linha", SqlDbType.Int);
                colunas[1] = new SqlMetaData("Ds_Linha", SqlDbType.NVarChar, 1024);
    
                var rec = new SqlDataRecord(colunas);
    
                // inicia o envio dos dados
                SqlContext.Pipe.SendResultsStart(rec);
    
                var contador = 1;
                while (sr.Peek() >= 0)
                {
    
                    // define as colunas
                    rec.SetInt32(0, contador);
                    rec.SetString(1, sr.ReadLine());
    
                    // Envia o registro para o banco de dados
                    SqlContext.Pipe.SendResultsRow(rec);
    
                    contador++;
                }
    
                // finaliza o envio dos dados
                SqlContext.Pipe.SendResultsEnd();
    
            }
        }
    }

Creando una función escalar

  • Haga clic derecho en el directorio “Funciones” > “Función escalar” del proyecto Bibliotecas (Biblioteca de clases) y seleccione la opción Agregar > Clase…. En la pantalla que se abre, escriba el nombre del archivo que se creará. Normalmente pongo el mismo nombre que el objeto que se creará en la base de datos. En este ejemplo crearé el archivo fncArquivo_Existe.cs
  • Copie y pegue el siguiente código:
    using System.Data.SqlTypes;
    using System.IO;
    
    public partial class UserDefinedFunctions
    {
        [Microsoft.SqlServer.Server.SqlFunction]
        public static SqlBoolean fncArquivo_Existe(SqlString Ds_Arquivo)
        {
            return (File.Exists(Ds_Arquivo.ToString()));
        }
    }
  • Tenga en cuenta que, a diferencia de los procedimientos que siempre devuelven void, las funciones deben devolver datos. En el caso del ejemplo, devuelven datos de tipo SqlBoolean (bit = true/false)

Crear una función con valores de tabla

  • Haga clic derecho en el directorio “Procedimientos” del proyecto Bibliotecas y seleccione la opción Agregar > Clase…. En la pantalla que se abre, escriba el nombre del archivo que se creará. Normalmente pongo el mismo nombre que el objeto que se creará en la base de datos. En este ejemplo, crearé el archivo fncArquivo_Ler.cs
  • Copie y pegue el siguiente código:
    using System.IO;
    using System.Collections;
    using System.Data.SqlTypes;
    
    public partial class UserDefinedFunctions
    {
    
        // Classe que irá receber os dados processados através do construtor da Classe
        private class ArquivoLer
        {
    
            public SqlInt32 Nr_Linha;
            public SqlString Ds_Texto;
    
            public ArquivoLer(SqlInt32 nrLinha, SqlString dsTexto)
            {
    
                Nr_Linha = nrLinha;
                Ds_Texto = dsTexto;
    
            }
    
        }
    
        // Definição da função, contendo o método de preenchimento dos registros (FillRow) 
        // e o retorno para o banco de dados
        [Microsoft.SqlServer.Server.SqlFunction(
            FillRowMethodName = "FillRow_Arquivo_Ler",
            TableDefinition = "Nr_Linha INT, Ds_Texto NVARCHAR(MAX)" /*,
            DataAccess = DataAccessKind.Read // Caso a função realize consultas no banco
            */
        )]
        public static IEnumerable fncArquivo_Ler(string Ds_Caminho)
        {
    
            var ArquivoLerCollection = new ArrayList();
    
            if (string.IsNullOrEmpty(Ds_Caminho))
                return ArquivoLerCollection;
    
            var contador = 1;
    
            using (var sr = new StreamReader(Ds_Caminho))
            {
    
                while (sr.Peek() >= 0)
                {
    
                    ArquivoLerCollection.Add(new ArquivoLer(
                        contador,
                        sr.ReadLine()
                    ));
    
                    contador++;
    
                }
    
                sr.Close();
    
            }
    
            return ArquivoLerCollection;
    
        }
    
    
        /*
    
        Método de preenchimento dos dados. O parâmetro de entrada é sempre o objeto da classe 
        criada no início do código e as variáveis que serão retornadas, todas como OUTPUT, pois 
        seus valores originais serão alterados para o método IEnumerable que controla a função.
    
        */
    
        public static void FillRow_Arquivo_Ler(object objArquivoLer, out SqlInt32 nrLinha, out SqlString dsTexto)
        {
    
            var ArquivoLer = (ArquivoLer)objArquivoLer;
    
            nrLinha = ArquivoLer.Nr_Linha;
            dsTexto = ArquivoLer.Ds_Texto;
    
        }
    
    }
  • Este tipo de objeto es probablemente el más difícil de crear en el CLR, ya que su resultado es una tabla y para llenar esta tabla, necesitamos crear una clase con conjuntos/obtenciones con los datos que serán devueltos, definir la salida de los datos a la base de datos y la programación para poblar/calcular los datos.

cadena o SqlString?

Haga clic aquí para ver
Como habrás notado en los ejemplos anteriores, en algunos casos uso string, bool, int, etc. y en otros uso SqlString, SqlBoolean, SqlInt32, etc. Esta diferencia es que los primeros casos son tipos de datos C#, mientras que los otros están dirigidos a la propia base de datos y tienen más controles y formas de trabajar.

Ejemplos:
El tipo SqlString tiene el método de verificación IsNull para determinar si se pasó un valor NULL como parámetro. Esta verificación es más rápida que usar string.IsNullOrEmpty. Sin embargo, utilizando una variable de cadena como parámetro, si la función o SP se llama con un valor NULL, se ejecutará normalmente, sea o no tratado en el futuro en su código fuente. Si ingresas un valor NULL para una variable de tipo SqlString y no has realizado el tratamiento usando el método variavel.IsNull, al recuperar el valor ingresado usando el atributo variavel.Value o el método variavel.ToString(), se generará una excepción ERROR en tu rutina.

Otro buen ejemplo para ilustrar la diferencia es entre DateTime y SqlDateTime. Si pasa una fecha NULL a DateTime, se generará una excepción ERROR en su rutina. Este tipo de datos no acepta NULL. Si necesita usar algo como esto, debe usar el método MinValue (1/1/0001 12:00:00 a.m.) o MaxValue (31/12/9999 23:59:59). El tipo de datos SqlDateTime acepta valores nulos y tiene el método variavel.Null para definir valores nulos.

Mi recomendación es utilizar siempre los tipos de datos de la base de datos (SqlString, SqlInt32, etc.) y recordar SIEMPRE realizar los tratamientos necesarios (principalmente la variable.IsNull).

¿Qué versión de .NET Framework debo usar?

Haga clic aquí para ver
Identificar y decidir qué versión de .NET Framework usar es una tarea muy sencilla, pero me ha causado muchos dolores de cabeza en el pasado debido a la falta de documentación sobre el tema. Por eso, intentaré ayudarte simplificando:
– SQL Server 2005: sólo puede utilizar .NET Framework 1.0 y 2.0
– SQL Server 2008: admite hasta .NET Framework 3.5
– SQL Server 2012 y 2014: admite hasta .NET Framework 4.6.1

Puede configurar la versión de .NET Framework al crear el proyecto o haciendo clic derecho en el proyecto de la Biblioteca de clases y seleccionando la opción "Propiedades". También debes realizar el mismo procedimiento para el proyecto CLR, que aún te brinda la posibilidad de definir la versión de la base de datos:

CLR - Target Framework
CLR - Marco de destino

CLR - Target Plataform
CLR - Plataforma de destino

CLR Class Library - .NET Framework Version
Biblioteca de clases CLR: versión .NET Framework

Compilando su proyecto y publicándolo en la base de datos.

Haga clic aquí para ver
Una vez desarrollados los códigos, publicaremos los ensamblados generados en la base de datos para comenzar a utilizar los objetos que se crearán. Puede configurar Visual Studio para crear la base de datos de destino (si no existe) o puede crear la base de datos manualmente primero (recomiendo esta opción) y luego publicar los ensamblados.

Una vez creada la base de datos, haga clic derecho en el proyecto CLR y seleccione la opción “Publicar…”. Los botones “Build” y “Rebuild” se utilizan para simplemente compilar el código fuente y validar si hay errores de sintaxis en el código fuente, mientras que el botón “Clean” elimina los archivos generados y el caché de Visual Studio.

CLR - Publish
CLR - Publicar

En la pantalla que se abrirá deberás hacer clic en el botón “Editar…” para ingresar al servidor y cómo conectarte a la base de datos.

CLR - Publish Database 2
CLR - Publicar base de datos 2

Puede crear/cargar perfiles para facilitar la publicación en más de un entorno. El botón "Generar scripts" generará un script SQL, que debe ser ejecutado por SQLCMD para publicar el CLR y el botón "Publicar" generará el script y lo ejecutará en la base de datos.

CLR Publish completed successfully
La publicación CLR se completó con éxito

Nivel de permiso de montaje

En SQLCLR, existen 3 niveles de permisos para los ensamblados creados:

  • SEGURO: Los métodos ensambladores pueden hacer más que los métodos Transact-SQL con la misma lógica y se ejecutan con las credenciales del usuario que llama.
  • ACCESO EXTERNO: Los métodos pueden realizar manipulación de archivos y operaciones de E/S a través de la red. Los métodos se ejecutan utilizando la cuenta de servicio de SQL Server, heredando sus privilegios de Active Directory.
  • INSEGURO / SIN RESTRICCIONES: amplía los privilegios de ACCESO EXTERNO, lo que permite que CLR ejecute comandos sin restricciones

Si el ensamblado necesita utilizar los niveles de permiso EXTERNAL ACCESS o UNRESTRICTED, será necesario definir la base de datos en la que será publicado como TRUSTWORTY:

ALTER DATABASE [Nome_do_Database] SET TRUSTWORTHY ON

Permisos necesarios para compilar el CLR

Para que un usuario tenga permiso para publicar un CLR en la base de datos, deberá cumplir uno de los siguientes requisitos:

  • Miembro del rol de administrador de sistemas
  • Si el nivel de permiso de ensamblaje es SEGURO, el usuario necesitará el permiso CREATE ASSEMBLY y DROP ASSEMBLY.
  • Si el nivel de permiso de ensamblaje es ACCESO EXTERNO, el usuario necesitará los permisos CREATE ASSEMBLY, DROP ASSEMBLY y EXTERNAL ACCESS ASSEMBLY.
  • Si el nivel de permiso de ensamblaje es SIN RESTRICCIONES, el usuario necesitará los permisos CREATE ASSEMBLY, DROP ASSEMBLY y UNSAFE ASSEMBLY.

Resultado final:

CLR - Object Explorer
CLR - Explorador de objetos

SQLCLR
SQLCLR

Restricciones en ensamblados: DLL admitidas y no admitidas

Haga clic aquí para ver
SQL Server impone ciertas restricciones al código administrado en ensamblados para verificar que se puedan ejecutar de manera segura y escalable. Esto significa que ciertas operaciones que podrían comprometer la solidez del servidor no están permitidas en los ensamblados SAFE y EXTERNAL_ACCESS.

Atributos personalizados no permitidos
Los ensamblajes no se pueden anotar con los siguientes atributos personalizados:
– System.ContextStaticAttribute
– System.MTAThreadAttribute
– System.Runtime.CompilerServices.MethodImplAttribute
– System.Runtime.CompilerServices.CompilationRelaxationsAttribute
– System.Runtime.Remoting.Contexts.ContextAttribute
– System.Runtime.Remoting.Contexts.SynchronizationAttribute
– System.Runtime.InteropServices.DllImportAttribute
– Sistema.Seguridad.Permisos.CodeAccessSecurityAttribute
– System.STAThreadAttribute
– System.ThreadStaticAttribute

Además, los ensamblados SAFE y EXTERNAL_ACCESS no se pueden anotar con los siguientes atributos personalizados:
– System.Security.SuppressUnmanagedCodeSecurityAttribute
– System.Security.UnverifiableCodeAttribute

API de .NET Framework no permitidas
Cualquier API de Microsoft .NET Framework anotada con una de las prohibiciones de HostProtectionAttributes no se puede llamar desde los ensamblados SAFE y EXTERNAL_ACCESS.
– eSelfAffectingProcessMgmt
– eSelfAffectingThreading
– eSincronización
– eSharedState
– eGestión de procesos externos
– eThreading externo
– Infraestructura de seguridad electrónica
– eMayLeakOnAbort
– IIU

Ensamblados de .NET Framework compatibles
Cualquier ensamblado al que haga referencia su ensamblado personalizado debe cargarse en SQL Server mediante CREATE ASSEMBLY. Los siguientes ensamblados de .NET Framework ya están cargados en SQL Server y, por lo tanto, se pueden consultar ensamblados personalizados sin tener que usar CREATE ASSEMBLY.
– CustomMarshalers.dll
– Microsoft.VisualBasic.dll
– Microsoft.VisualC.dll
– mscorlib.dll
– Sistema.dll
– Configuración.del.sistema
– System.Core.dll (compatible con SQL Server 2008)
– System.Data.dll
– Sistema.Datos.OracleClient
– System.Data.SqlXml.dll
– Implementación.del.sistema
– System.Security.dll
– Sistema.Transacciones
– System.Web.Services.dll
– System.Xml.dll
– System.Xml.Linq.dll (compatible con SQL Server 2008)

Haga clic aquí para ver
-- SYS.ASSEMBLIES
-- Name, Assembly ID, security and “is_visible” flag
SELECT * FROM sys.assemblies

 
-- SYS.ASSEMBLY_FILES
-- Assembly ID, name of each file & assembly contents
SELECT * FROM sys.assembly_files
 

-- SYS.ASSEMBLY_MODULES
-- Sql ObjectID, Assembly ID, name & assembly method
SELECT * FROM sys.assembly_modules
 

-- SYS.ASSEMBLY_REFERENCES
-- Links between assemblies on Assembly ID
SELECT * FROM sys.assembly_references
 

-- SYS.MODULE_ASSEMBLY_USAGES
-- Partial duplicate of SYS.ASSEMBLY_MODULES
-- Links SQL Object ID to an Assembly ID
SELECT * FROM sys.module_assembly_usages


-- CLR STORED PROCEDURES
SELECT
    SCHEMA_NAME(sp.schema_id) + '.' + sp.[name] AS [Name],
    sp.create_date,
    sp.modify_date,
    sa.permission_set_desc AS [Access],
    sp.is_auto_executed
FROM
    sys.procedures AS sp
    INNER JOIN sys.module_assembly_usages AS sau ON sp.object_id = sau.object_id
    INNER JOIN sys.assemblies AS sa ON sau.assembly_id = sa.assembly_id
WHERE
    sp.type_desc = N'CLR_STORED_PROCEDURE';


-- CLR TRIGGERS
SELECT
    SCHEMA_NAME(so.schema_id) + '.' + tr.[name] AS [Name],
    SCHEMA_NAME(so.schema_id) + '.' + OBJECT_NAME(tr.parent_id) AS [Parent],
    te.type_desc AS [Fired On],
    te.is_first,
    te.is_last,
    tr.create_date,
    tr.modify_date,
    sa.permission_set_desc AS [Access],
    tr.is_disabled,
    tr.is_not_for_replication,
    tr.is_instead_of_trigger
FROM
    sys.triggers AS tr
    INNER JOIN sys.objects AS so ON tr.[object_id] = so.[object_id]
    INNER JOIN sys.trigger_events AS te ON tr.[object_id] = te.[object_id]
    INNER JOIN sys.module_assembly_usages AS mau ON tr.object_id = mau.object_id
    INNER JOIN sys.assemblies AS sa ON mau.assembly_id = sa.assembly_id
WHERE
    tr.type_desc = N'CLR_TRIGGER'


-- CLR Scalar Functions
SELECT
    SCHEMA_NAME(so.schema_id) + N'.' + so.[name] AS [Name],
    so.create_date,
    so.modify_date,
    sa.permission_set_desc AS [Access]
FROM
    sys.objects AS so
    INNER JOIN sys.module_assembly_usages AS sau ON so.object_id = sau.object_id
    INNER JOIN sys.assemblies AS sa ON sau.assembly_id = sa.assembly_id
WHERE
    so.type_desc = N'CLR_SCALAR_FUNCTION'


-- CLR Table-valued Functions
SELECT
    SCHEMA_NAME(so.schema_id) + N'.' + so.[name] AS [Name],
    so.create_date,
    so.modify_date,
    sa.permission_set_desc AS [Access]
FROM
    sys.objects AS so
    INNER JOIN sys.module_assembly_usages AS sau ON so.object_id = sau.object_id
    INNER JOIN sys.assemblies AS sa ON sau.assembly_id = sa.assembly_id
WHERE
    so.type_desc = N'CLR_TABLE_VALUED_FUNCTION'


-- CLR Aggregate Function
SELECT
    SCHEMA_NAME(so.schema_id) + N'.' + so.[name] AS [Name],
    so.create_date,
    so.modify_date,
    sa.permission_set_desc AS [Access]
FROM
    sys.objects AS so
    INNER JOIN sys.module_assembly_usages AS mau ON so.object_id = mau.object_id
    INNER JOIN sys.assemblies AS sa ON mau.assembly_id = sa.assembly_id
WHERE
    so.type_desc = N'AGGREGATE_FUNCTION'


EXEC sys.sp_assemblies_rowset N'<AssemblyName>'
EXEC sys.sp_assembly_dependencies_rowset <AssemblyID>

Vídeo: Introducción a SQLCLR

¿Quieres hablarnos sobre SQLCLR?
– Telegrama: https://t.me/sqlclr
– Whatsapp: https://chat.whatsapp.com/71JS49CiD12Ct6pR3fjfLu

¡Y eso es todo, amigos!
¡Hasta el próximo post!

CLR SQL Server SQLCLR cómo activar habilitar cómo usar cómo activar habilitar cómo crear sp cómo crear un procedimiento cómo programar cómo comenzar introducción comenzar por dónde empezar cómo codificar procedimientos de programación funciones con valores de tabla escalar c# programación csharp base de datos programación de bases de datos

CLR SQL Server SQLCLR cómo activar habilitar cómo usar cómo activar habilitar cómo crear sp cómo crear un procedimiento cómo programar cómo comenzar introducción comenzar por dónde empezar cómo codificar procedimientos de programación funciones con valores de tabla escalar c# programación csharp base de datos programación de bases de datos