Twitter Facebook RSS Feed

lunes, 17 de diciembre de 2012 a las 21:34hs por Dario Krapp

En este artículo vamos a ver una introducción de lo que se conoce como SQL CLR que es básicamente, la posibilidad que ofrece SQL Server de ejecutar código .NET, en este caso vamos a utilizar SQL Server 2008 Express y C# con Visual Studio 2010.

A grandes rasgos lo que vamos a hacer para conseguir nuestro objetivo es crear un tipo particular de proyecto en Visual Studio 2010 llamado Visual C# SQL CLR Database Project, escribiremos algunas líneas de código en C#, generaremos una dll .NET que luego incorporaremos en una base de datos del motor SQL Server 2008 Express a través de unos pocos comandos, finalmente accederemos a las funcionalidades de esta dll desde SQL Server.

Pre-ajustes

El primer paso que deberemos realizar es activar la posibilidad de ejecutar código .NET en el servidor de base de datos, para eso ejecutaremos la siguiente sentencia:

Para completar la activación deberemos también ejecutar lo siguiente:

Para los lectores que hayan llegado hasta este punto, les comento que pueden ejecutar ambas sentencias de una vez de la siguiente forma:

sp_configure 'clr enabled', 1
GO
RECONFIGURE
GO

C# y Visual Studio

Split Mundo

Una vez hecho esto crearemos el proyecto .NET, para eso iremos al Visual Studio 2010 y crearemos un proyecto del tipo Visual C# SQL CLR Database Project, tal como se muestra a continuación:

La creación del proyecto nos pedirá que ingresemos un servidor SQL Server y una base de datos de prueba, el único cuidado que debemos tener es que el Target Framework, deberá ser 2.0, 3.0, 3.5, otras versiones de Framework no funcionarán, esto es por que el CLRde SQL Server es cargado sobre el Framework 2.0.
En nuestro ejemplo hemos creado la base de datos TEST sobre un servidor SQL Server 2008 Express, sobre este servidor desplegaremos el proyecto en el proceso de testing.
Una vez creado el proyecto, las opciones serán bastante intuitivas, ya que presionando el botón derecho del mouse sobre el proyecto en la opcion Add (o Agregar dependiendo del idioma del VS) encontraremos las siguientes opciones:

Simplemente deberemos seleccionar el tipo de objeto SQL Server que deseemos crear, casi igual que si estuviesemos en el SQL Server Management Studio, crearemos para empezar un stored procedure que permita realizar un Split sobre sobre una entrada de texto (ya que el «Hola Mundo» se ha vuelto muy popular y poco emocionante).

Seleccionaremos entonces la opción de agregar un stored procedure y un nuevo elemento aparecerá en el árbol del proyecto con el nombre que le hayamos designado (oportunamente ClrSplit.cs en nuestro caso), el stored procedure se implementará como una clase parcial «StoredProcedures» con un método estático con el nombre ClrSplit para nuestro ejemplo.

Para simplificar la explicación comento que al abrir el código nos encontraremos con lo siguiente:

[Microsoft.SqlServer.Server.SqlProcedure]
public partial class StoredProcedures
{
    [Microsoft.SqlServer.Server.SqlProcedure]
    public static void ClrSplit()
    {
    }
}

Básicamente cada nuevo store procedure que vayamos agregando, se irá agregando a la clase «StoredProcedures» en un nuevo archivo gracias a la posibilidad que ofrece .NET de crear clases parciales (o sea repartidas en varios archivos) y en cada archivo Visual Studio generará el esqueleto de la función para que nosotros lo ajustemos a nuestras necesidades.

Lo intuitivo, sigue siendo aún más intuitivo ya que podremos modificar la función en C# en la forma habitual y todo funcionará casi a la perfección, en nuestro ejemplo, modificaremos el código de la siguente forma:

[Microsoft.SqlServer.Server.SqlProcedure]
public partial class StoredProcedures
{
    [Microsoft.SqlServer.Server.SqlProcedure]
    public static void ClrSplit(string Text, string Separator)
    {
        string[] SplitedList = Text.Split(new string[] { Separator }, StringSplitOptions.None);
    }
}

Y con esto, ya tenemos la lista de palabras separadas desarrollada y funcionando, el único paso es ahora pasarle al servidor SQL Server el resultado, para esta tarea contamos con la clase SqlPipe que posee una variedad de métodos que nos permitirán pasarle resultados a SQL Server de forma bastante sencilla, esto es lo que hacemos a continuación:

[Microsoft.SqlServer.Server.SqlProcedure]
public static void ClrSplit(string Text, string Separator)
    {
        string[] SplitedList = Text.Split(new string[] { Separator }, StringSplitOptions.None);
        SqlDataRecord record = new SqlDataRecord(new SqlMetaData("SplitedValue", SqlDbType.NVarChar, -1));
        SqlContext.Pipe.SendResultsStart(record);
        foreach (string SplitedValue in SplitedList)
        {
            record.SetString(0, SplitedValue);
            SqlContext.Pipe.SendResultsRow(record);
        }
        SqlContext.Pipe.SendResultsEnd();
    }

Lo que hacemos en el código es definir los campos que tendrá la salida en un objeto SqlDataRecord, en nuestro caso tendremos una sola columna del tipo NVarChar(MAX) y luego escribiremos los resultados utilizando algunos métodos proporcionados por la clase SqlPipe, los métodos SendResultsStart y SendResultsEnd indicarán en inicio y fin del envío de registros, mientras que el método SendResultsRow añadirá un registro a la salida. La creación y carga de datos en los registros tampoco es muy compleja.

Con esto hemos terminado nuestro ejemplo, lo que debemos hacer ahora es pasarlo a nuestro servidor de testing SQL Server 2008 Express. Para hacer esto solo debemos presionar el botón derecho del mouse en el proyecto y seleccionar la opción Deploy, como ya habiamos especificado un servidor SQL Server al inicio del proyecto, Visual Studio podrá desplegar la dll en el mismo sin inconvenientes y cuando accedamos a nuestro servidor nos encontraremos con que todo ha funcionado sin problemas.

Entre la lista de stored procedures encontraremos ClrSplit y podremos invocarlo y obtener los resultados como cualquier procedimiento habitual.

Si miramos detalladamente el stored procedure en nuestro servidor SQL Server notaremos, creo yo antes que nada, que los parámetros definidos en el método desarrollado en C# son parte de los parámetros que está tomando el procedimiento almacenado en SQL Server y que los tipos de datos han sido mapeados automáticamente de string a NvarChar(4000) tal como puede verse en la última imagen. Para quienes estén acostumbrados a trabajar con C# y SQL Server sabrán que algunos mapeos entre ambos son univocos, por ejemplo byte versus tinyint o short versus smallint, pero otros mapeos no son tan claros ya que en algunos casos SQL Server admite una mayor atomización de datos y a la hora de mapear un parámetro string de C# a algo de SQL Server, aunque sabemos que será hacia un NVarChar (ya que los strings de C# son Unicode), es posible que la longitud de la conversión no sea la deseada.

Por lo que hemos visto, la conversión por defecto ha sido a un NvarChar(4000) que es bastante lógico, debido a que estos campos son el mayor campo de texto posible en SQL Server (al menos el mayor texto se guarda en SQL Server como un texto en un registro, ya que los campos NVarChar(MAX) más allá de que el nombre nos confunda son diferentes a los campos NVarChar con límite ya que se almacenan de otra forma y no admiten todas las operaciones de los campos NVarChar con límite e incluso en versiones previas, y aún ahora también, se conocían como campos NText).

Más allá de estos detalles, lo concreto es que podríamos desear cambiar el algunas características del tipo de dato que vamos a pasar al stored procedure, para estas necesidades podrá ayudarnos la clase SqlFacetAttribute que la utilizaremos como un atributo en el método C#, entonces si quisiéramos que los parámetros Text y Separator de nuestro ejemplo se ajusten a NVarChar(MAX) y NVarChar(5) consecutivamente deberemos utilizar la clase SqlFacetAttribute como un atributo de la siguiente forma:

[Microsoft.SqlServer.Server.SqlProcedure]
    public static void ClrSplit([SqlFacet(MaxSize = -1)]string Text, [SqlFacet(MaxSize = 5)]string Separator)
    {
        string[] SplitedList = Text.Split(new string[] { Separator }, StringSplitOptions.None);
        SqlDataRecord record = new SqlDataRecord(new SqlMetaData("SplitedValue", SqlDbType.NVarChar, -1));
        SqlContext.Pipe.SendResultsStart(record);
        foreach (string SplitedValue in SplitedList)
        {
            record.SetString(0, SplitedValue);
            SqlContext.Pipe.SendResultsRow(record);
        }
        SqlContext.Pipe.SendResultsEnd();
    }

Esta clase también incluirá propiedades para hacer ajustes en decimales, en el siguiente enlace: Atributos para rutinas CLR (MSDN) puede encontrarse información sobre la clase SqlFacetAttribute tanto como sobre otras clases «atributo» aplicables a otros elementos, como por ejemplo la clase SqlFunctionAttribute.

No veremos todas estas clases aunque si las retomaremos un poco más adelante. Volviendo a nuestras últimas líneas de código veremos que luego de agregar:

public static void ClrSplit([SqlFacet(MaxSize = -1)]string Text, [SqlFacet(MaxSize = 5)]string Separator)

Y desplegar nuevamente la aplicación, El stored procedure habrá modificado los parámetros adecuadamente. Ahora sí creo que podemos decir que todo quedó como esperábamos.

Avanzando un poco más

Si revismos lo hicimos hasta ahora, imagino que podrían suceder dos cosas, la primera es que nos quedemos con lo que tenemos y utilicemos el stored procedure ClrSplit de aquí en adelante sin mayores inconvenientes. Por otra parte podría suceder que nos parezca que en realidad nuestro stored procedure ClrSplit no debería ser un stored procedure ya que se comporta como una función, digamos que una función del tipo Table-Valued Function ya que debería retornar una tabla con las palabras resultado.

Si están de acuerdo con esta idea, el primer paso será borrar la clase ClrSplit (stored procedure) y crear ahora la clase ClrSplit (user defined function), para hacer esto luego de eliminar ClrSplit (stored procedure) debemos presionar el botón derecho del mouse a nivel de proyecto, seleccionar el ítem Add y luego el ítem User-Defined Function, y la clase ClrSplit aparecerá en el árbol del proyecto, si vamos la código veremos que es bastante similar a lo que teníamos previamente:

public partial class UserDefinedFunctions
{
    [Microsoft.SqlServer.Server.SqlFunction]
    public static SqlString ClrSplit()
    {
        // Put your code here
        return new SqlString("Hello");
    }
};

Si nos olvidamos por un momento de lo que nos habíamos propuesto realizar, podemos ver que el código sigue siendo bastante intuitivo, en este código de ejemplo que crea automáticamente Visual Studio podemos ver la semántica de la función, donde la misma no tendrá que hacer otra cosa más que retornar un valor, como cualquier función tradicional C# pero en clases destinadas a tal fin, o sea que no podemos retornar un string directamente, deberemos emplear un SqlString. Si revisamos un poco el namespace System.Data.SqlTypes veremos que hay soporte para retornar una amplia cantidad de tipos de datos.

Si desplegamos este código de ejemplo, veremos que funciona y que sin problemas podremos ajustarlo a lo que necesitemos, aunque seguramente también notemos que las funciones que podemos desarrollar son aquellas que retornan un valor escalar, o sea aquellas que son denominadas Scale-Valued Functions.

Si recobramos la memoria recordaremos que lo que necesitabamos hacer es desarrollar una función que retorne una tabla con las palabras divididas, a esta altura sabremos con certeza que ninguna Scale-valued Function nos será de utilidad. A continuación desarrollaremos la función TVF que necesitamos y luego explicaremos de que se trata.

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Collections;
using System.Collections.Generic;

public partial class UserDefinedFunctions
{

    [SqlFunction(Name = "ClrSplit", DataAccess = DataAccessKind.Read, FillRowMethodName = "FillRowWithSplitedValue", TableDefinition = "SplitedValue NVARCHAR(MAX)")]
    public static IEnumerable ClrSplit([SqlFacet(MaxSize = -1)]string Text, [SqlFacet(MaxSize = 5)]string Separator)
    {
        string[] SplitedList = Text.Split(new string[] { Separator }, StringSplitOptions.None);
        SqlDataRecord record = new SqlDataRecord(new SqlMetaData("SplitedValue", SqlDbType.NVarChar, -1));
        List<object[]> resultItems = new List<object[]>();
        foreach (string SplitedValue in SplitedList)
        {
            object[] item = new object[1];
            item.SetValue(SplitedValue, 0);
            resultItems.Add(item);
        }
        return resultItems;
    }

    public static void FillRowWithSplitedValue(Object obj, out SqlString SplitedValue)
    {
        object[] item = (object[])obj;
        SplitedValue = (SqlString)item[0].ToString();
    }

};

Si deplegamos y ejecutamos el proyecto, veremos que la función es agregada y podemos ejecutarla sin problemas.

En este caso, el código no es tan intuitivo, pero puede entenderse. Básicamente debemos devolver un IEnumerable (la clase List es una implementación de IEnumerable que nos viene perfecto) donde cada ítem es un Array de objetos y cada objeto del Array contiene el valor del campo. El atributo TableDefinition que podemos encontrar en CltSplit determinará el formato de la tabla que devolveremos y de más está decir que debe ser consistente con los Arrays de objetos mencionados previamente, por ejemplo:

TableDefinition = "SplitedValue NVARCHAR(MAX)"
object[] item = new object[1];
item.SetValue("Este es el valor de la columna SplitedValue para este registro", 0);

o

TableDefinition="Id int, Address nvarchar(4000)")]
object[] item = new object[2];
item.SetValue(1, 0);
item.SetValue("Este es el valor de la columna Address para este registro, la columna Id Vale 1", 1);

Son combinaciones consistentes.

Si seguimos revisando el código veremos que también hemos agregado la siguiente función:

public static void FillRowWithSplitedValue(Object obj, out SqlString SplitedValue)
    {
        object[] item = (object[])obj;
        SplitedValue = (SqlString)item[0].ToString();
    }

la función FillRowWithSplitedValue será invocada por SQL CLR cuando vaya avanzando y creando los registros de la tabla de salida, pasándonos el Array de objetos que nosotros mismos hemos creado en la funcion ClrSplit. FillRowWithSplitedValue deberá indicar a que columna corresponde cada valor del objeto, la forma de indicarle a esto SQL CLR es simplemente crear parámetros de salida (con el mismo tipo de datos y orden que la definición) y establecer el valor adecuadamente a partir del Array de objetos. En nuestro ejemplo tenemos una sola columna, pero si estuviéramos en este caso:

TableDefinition="Id int, Address nvarchar(4000)")]
object[] item = new object[2];
item.SetValue(1, 0);
item.SetValue("Este es el valor de la columna Address para este registro, la columna Id Vale 1", 1);

Nuestra, función debería adoptar siguiente forma:

public static void FillRow(object obj, out SqlInt32 I, out SqlString A)
    {
        object[] item = (object[])obj;
        I = (SqlInt32)(int) item[0];
        A = (SqlString)item[1].ToString();
    }

El atributo FillRowMethodName es quién indica a SQL CRL el método de llenado de filas, en nuestro ejemplo:

[SqlFunction(Name = "ClrSplit", DataAccess = DataAccessKind.Read, FillRowMethodName = "FillRowWithSplitedValue", TableDefinition = "SplitedValue NVARCHAR(MAX)")]    public static IEnumerable ClrSplit([SqlFacet(MaxSize = -1)]string Text, [SqlFacet(MaxSize = 5)]string Separator)
    {
        ....

podemos ver que estamos indicando que el método FillRowWithSplitedValue es que se ha establecido como funcion de llenado de filas.

El atributo Name por otra parte permitirá modificar el nombre con el cual la función se verá en SQL Server, ya que podríamos tener la necesidad de que no sea el mismo nombre que utilizamos en el código C#, ejemplo:

[SqlFunction(Name = "SplitVarChar", DataAccess = DataAccessKind.Read, FillRowMethodName = "FillRowWithSplitedValue", TableDefinition = "SplitedValue NVARCHAR(MAX)")]    public static IEnumerable ClrSplit([SqlFacet(MaxSize = -1)]string Text, [SqlFacet(MaxSize = 5)]string Separator)
    {
        ....

Finalmente el atributo DataAccess indicará si la función podrá leer datos del usuario almacenados en el servidor SQL Server. El valor deberá ser DataAccessKind.Read en el caso de utilizarse una función TVF, tal como se encuentra indicado en SqlFunctionAttribute.DataAccess Property (MSDN) para cualquier versión de Framework que estemos utilizando.

Dando otra vuelta por un stored procedure

En este punto, hemos podidio crear una función SQL CLR del tipo Table-Valued Field, tambien una Scale-Valued Field (en realidad hemos visto la que ha creado Visual Studio, aunque funcionó perfectamente sin nuestra intervención) y un stored procedure que luego transformamos en una función, por que nos pareció que la funconalidad no estaba demasiado relacionada a lo que suelen hacer los stored procedures.

Vamos a ahora retomar los stored procedures y vamos uno que ejecute un INSERT en una tabla, ya que esta funcionalidad si es usual en los stored procedures. Crearemos una tabla sencilla y escribiremos el stored procedure que insertará datos en la misma.

public partial class StoredProcedures
{
    [Microsoft.SqlServer.Server.SqlProcedure]
    public static void ClrInsertCompany([SqlFacet(MaxSize = 100)]string Name, DateTime CreationDate, short EmployeeNumber)
    {
        using (SqlConnection sqlConex = new SqlConnection("context connection=true"))
        {
            using (SqlCommand sqlCmd = new SqlCommand("INSERT INTO [Company] ([Name],[CreationDate],[EmployeeNumber]) VALUES (@Name,@CreationDate,@EmployeeNumber)", sqlConex))
            {
                try
                {
                    sqlConex.Open();
                    sqlCmd.Parameters.Add("@Name", SqlDbType.NVarChar, 100).Value = Name;
                    sqlCmd.Parameters.Add("@CreationDate", SqlDbType.DateTime).Value = CreationDate;
                    sqlCmd.Parameters.Add("@EmployeeNumber", SqlDbType.SmallInt).Value = EmployeeNumber;
                    sqlCmd.ExecuteNonQuery();

                }
                catch (SqlException ex)
                {
                    SqlContext.Pipe.Send("Error" + ex.Message);
                }
                finally
                {
                    sqlConex.Close();
                }
            }
        }
    }
};

Como puede verse, casi no hay diferencia entre este código y el código que se utiliza en cualquier acceso a datos desde C# en un proyecto tradicional, lo único que cambia es la forma de obtener el conection string.
Si ejecutamos el stored procedure desde Sql Server, notaremos que funciona sin inconvenientes.

Triggers

Ahora que ya tenemos un stored procedure y una función crearemos un trigger, que aunque no es uno de los objetos más populares, en ocasiones es necesario.
Existen dos tipos de triggers, los llamados triggers DML y los triggers DDL, y como es de imaginarse los primeros actuan sobre sentencias DML, como INSERT y DELETE mientras que los segundos actuan sobre sentencias DDL como por ejemplo CREATE_TABLE.
En este artículo, ya que es una introducción, nos preocuparemos por los triggers DML.

Como ya estamos prácticamente cerrando el artículo, vamos a agregar el trigger dándole al ejemplo un poco de dinamismo, vamos entonces a crear una tabla nueva que llamaremos CompanyLog que va a guardar la fecha y hora de cuando una empresa es creada, modificada o eliminada y un stored procedure en SQL Server que insertará un registro de inserción en esta tabla de log indicando la fecha de creación de la empresa, a continuación mostramos ambos objetos:

Finalmente vamos a crear un trigger en el proyecto C# tal como se muestra a continuación:

public partial class Triggers
{
    [Microsoft.SqlServer.Server.SqlTrigger(Name = "TriggerInsertCompany", Target = "Company", Event = "FOR INSERT")]
    public static void ClrTriggerInsertCompany()
    {
        SqlTriggerContext triggContext = SqlContext.TriggerContext;
        if (triggContext.TriggerAction == TriggerAction.Insert)
        {
            using (SqlConnection conn = new SqlConnection("context connection=true"))
            {
                conn.Open();
                SqlCommand sqlComm = new SqlCommand();
                sqlComm.Connection = conn;
                sqlComm.CommandText = "SELECT CompanyId from INSERTED";
                int CnyId = (int)sqlComm.ExecuteScalar();

                sqlComm.Parameters.Clear();
                sqlComm.CommandText = "CompanyLogInsertionLog";
                sqlComm.CommandType = CommandType.StoredProcedure;
                sqlComm.Parameters.Add("@CompanyId", SqlDbType.Int).Value = CnyId;
                sqlComm.ExecuteNonQuery();
            }
        }
         
    }
}

Cuando un valor es insertado en la tabla Company el trigger de inserción ClrTriggerInsertCompany es disparado, el mismo toma el valor del campo CompanyId insertado en la tabla Company e invoca al stored procedure CompanyLogInsertionLog que registra la creación en la tabla CompanyLog, es complicado de explicar, pero fácil de entender. Si invocamos a nuestro antiguo stored procedure ClrInsertCompany que habíamos desarrollado hace unas líneas atrás e insertaba un registro en la tabla Company veremos que un registro es efectivamente insertado en la tabla Company pero además otro registro es insertado también en la tabla CompanyLog indicando que todo el circuito funciona. La implementción de los triggers UPDATE y DELETE la vamos a omitir, ya que no aportan nada nuevo.

El paso final

Ahora que hemos completado las funcionalidades que queríamos lograr, el paso final será desplegar estas funcionalidades en un servidor. Hasta el momento Visual Studio era quien venía realizando esta tarea en nuestra base de datos de pruebas cada vez que realizabamos un Deploy. Si accedemos a la carpeta Assemblies de nuestro SQL Server veremos que allí se encuentra nuestra dll (SqlServerProject1.dll).

El paso final es desplegar el assembly en un servidor SQL Server tal como lo haríamos en un servidor de producción una vez que las funcionalidades estuviesen completas. Para efectuar esta tarea debemos copiar nuestra dll y registrar el assembly desde SQL Server, hemos creado una base de datos llamada STAGE, hemos pasado las tablas y el store procedure CompanyLogInsertionLog. Luego hemos ejecutado la siguiente sentencia:

CREATE ASSEMBLY 
	StageSqlServerProject1 
FROM 'C:\SQLAssembly\SqlServerProject1.dll' 
WITH PERMISSION_SET = SAFE;

Una vez hecho esto sobre la base de datos STAGE veremos que la dll aparece en la lista de elementos de la carpeta Assemblies.
Un detalle es que en la última sentencia le hemos asignado el nombre StageSqlServerProject1 al assembly en nuestra base de datos de STAGE:

Otro detalle es que hemos registrado el assembly utilizando el conjunto de permisos denominado SAFE, este es el valor por defecto y hace referencia a la seguridad que se aplicará sobre el assembly. SAFE es la opcion más restrictiva de seguridad, las otras posibilidades son EXTERNAL_ACCESS que permite al assembly acceder a recursos externos como archivos, red, registry, etc y UNSAFE que le permite al assembly ejecutar código no manejado.

Aunque el assembly fué importado aún no podemos encontrar las funciones, el stored procedures y el triggers que hemos creado. Es necesario además ejecutar unas líneas extra para incluir:

El stored procedure ClrInsertCompany

EXEC dbo.sp_executesql @statement = N'CREATE PROCEDURE [dbo].[ClrInsertCompany]
	@Name [nvarchar](100),
	@CreationDate [datetime],
	@EmployeeNumber [smallint]
AS
EXTERNAL NAME [StageSqlServerProject1].[StoredProcedures].[ClrInsertCompany]'

La función ClrSplit que habiamos renombrado como SplitVarChar en C#

execute dbo.sp_executesql @statement = N'CREATE FUNCTION [dbo].[SplitVarChar](@Text [nvarchar](max), @Separator [nvarchar](5))
RETURNS  TABLE (
	[SplitedValue] [nvarchar](max) NULL
) WITH EXECUTE AS CALLER
AS 
EXTERNAL NAME [StageSqlServerProject1].[UserDefinedFunctions].[ClrSplit]'

Y el trigger que habiamos llamdo TriggerInsertCompany

CREATE TRIGGER [dbo].[TriggerInsertCompany] ON [dbo].[Company]  AFTER  INSERT AS 
EXTERNAL NAME [StageSqlServerProject1].[Triggers].[ClrTriggerInsertCompany]

Una vez hecho esto, podremos invocar al stored procedure ClrInsertCompany en la base de datos STAGE y veremos que todo funciona al igual que en nuestra base de datos de testing.
De más está decir que es válido utlizar la herramienta de generación de scripts que provee el SQL Server Management Studio para ayudarnos a crear los scripts, solo debemos recordar ajustar el nombre del assembly al que le hayamos definido en la sentencia de importación (StageSqlServerProject1 en nuestro ejemplo).

Analisis y comentarios finales

En este artículo hemos visto una introducción a la utilización de SQL CLR y hemos creado los objetos más utilizados en SQL Server, stored procedures, funciones y un trigger.
SQL CLR es mucho más extenso y permite crear otros tipos de objetos, tales como las funciones de agregación «Aggegate Functions» y tipos de datos de usuario. Ambos quedan fuera del alcance de este artículo.
El último comentario es que como con todas las tecnologías antes de aplicarlas es importante en primera instancia ver en que casos dicha tecnología es conveniente y ventajosa, en este caso es claro que el uso de SQL CLR será deseable cuando tengamos funcionalidades que no podamos desarrollar con SQL Server, o funcionalidades que ya se encuentran desarrolladas en el .NET Framework, tambien SQL CLR es toma ventajas o cuando sea necesario interactuar con el sistema operativo dentro del motor de base de datos.
SQL CLR también puede ayudar a incluir las reglas de negocios dentro de la base de datos en una forma más simple y fácil de mantener que con T-SQL. Obviamente el uso de SQL CLR restringe la posibilidad de migración a otras bases de datos y debería evaluarse si puede aplicarse en el servidor SQL Server de producción donde se desplegará la aplicación, ya que por ejemplo los hostings suelen ser bastantes restrictivos.

Finalizando el artículo, espero que sea de utilidad para quienes tengan la posibilidad de utilizar esta tecnología.
Hasta el proximo artículo!!

7 comentarios »

  1. Cesar Salazar dice:

    He creado algunas funciones usando este método que me ha parecido muy bien, mi pregunta es ¿Cómo puedo modificar una función que esta siendo referenciada en un campo de una tabla, sin tener que borrar dicho campo?

    Dicho de otro modo, he creado un campo que realiza una operación matemática desde una función de usuario, he agregado mas funciones a la DLL pero ahora, no puedo implementarlas, ya que me dice que una función está bloqueada pues se esta usando por una tabla de la base de datos.

    Muchas gracias!

    • Dario Krapp dice:

      Hola Cesar,

      Más allá de que la UDF la hayas creado vía SQL CLR (o sea que aunque la hubieses creado via T-SQL sería lo mismo), si la misma se encuentra en un campo de una tabla no es posible eliminarla. Aunque luego de varias búsquedas por el MSDN nunca encontré información que dijera específicamente que tampoco es posible modificar una UDF mientras se encuentre referenciada por un campo en una tabla, la verdad es nunca encontré la forma de hacerlo y las formas siempre estuvieron relacionadas a eliminar la relación con la formula y luego reestablecerla, lo cual es bastante incómodo.
      Suerte y slds

  2. Sebastian Vasquez Libreros dice:

    Excelente información. Te felicito por brindar este conocimiento a la comunidad ya que esta super completa y me ha servido de mucho. Gracias por todo 🙂

  3. Jose dice:

    Te felicitao me ha servido.

  4. Buenas estoy creando un Trigger CLR y dentro del codigo necesito referenciar un dll, cuando la quiero agregar en la base de datos me da este error,

    CREATE ASSEMBLY DLLAfip
    AUTHORIZATION [dbo]
    FROM 'C:\FEAFIPLib.dll'
    WITH PERMISSION_SET = UNSAFE
    GO

    Error de CREATE ASSEMBLY para el ensamblado ‘FEAFIPLib’ porque se ha generado para una versión incompatible de Common Language Runtime.

    valido la version del CLR en sql server

    select * from sys.dm_clr_properties

    directory D:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\
    version v2.0.50727
    state CLR is initialized

    vomo puedo hacer para modificar la version del CLR en el sql ya que en la DLL no la puedo cambiar, instale el SP1 y SP2 de sql server 2008 pero sigue figurando la version v2.0.50727.

    Espero puedan ayudarme.
    Saludos Leandro.

  5. pinguino dice:

    Tengo sql server 2012 en sp2, dicho esto. ¿Me vale este procedimiento para resolver este error?

    2020-01-22 23:25:22;spid20s;AppDomain 14 (instancia.clrTemp[runtime].14) is marked for unload due to memory pressure.

    2020-01-22 23:25:22;spid20s;AppDomain 14 (instancia.clrTemp[runtime].14) unloaded.

    2020-01-22 23:25:26;spid334;AppDomain 15 (instancia.clrTemp[runtime].17) created.

Deja un comentario

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.