martes, 9 de julio de 2013

Validando email en SQL



En ocasiones necesitamos verificar que un correo electrónico tenga un formato válido, es decir una bandeja y su dominio respectivos divididos por una arroba.

En esta entrada veremos una forma sumamente simple pero que servirá como punta de lanza para que utilices el potencial que tienen los ensablados del .NET Framework para integrarlos en SQL Server.

Hay que decir y dejar muy claro que no es la panacea para las cosas complicadas de SQL, es simplemente un punto más, una herramienta más que tenemos a la mano para poder utilizar y facilitar nuestro trabajo.

Hacer la función que valide el formato del correo electrónico se podría describir en un proceso muy simple:
  1. Crear la función como estática en .NET.
  2. Crear un objeto ASSEMBLY en nuestra base de datos.
  3. Crear la función que será mapeada hacia un elemento almacenado en el ASSEMBLY.
Creando función en .NET

Vamos a crear un proyecto de tipo Class Library en Visual Studio y eliminaremos el archivo Class1.cs que trae por defecto la plantilla.

Vamos agregar una clase que alojará nuestra función estática que realizará la validación del correo electrónico, el código quedaría de la siguiente manera:

using Microsoft.SqlServer.Server;
using System.Data.SqlTypes;
using System.Text.RegularExpressions;

namespace EmailValidator
{
    public class Validators
    {
        [SqlFunction]
        public static SqlBoolean ValidateEmail(SqlString email)
        {
            return SqlBoolean.Parse(Regex.IsMatch(email.Value, @"^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$").ToString());
        }
    }
}


El atributo SqlFunction que está calificando a la función ValidateEmail le hace saber a SQL Server que ahí hay una función que puede utilizarse.

Debemos manejar tipos de dato de SQL Server, es por ello que la función devuelve un valor de tipo SqlBoolean y recibe una cadena del tipo SqlString los cuales están definidos en el espacio de nombres System.Data.SqlTypes.

La clase RegEx está definida en el espacio de nombres System.Text.RegularExpressions y su función IsMatch devuelve un valor booleano indicando que su primer parámetro cumple con el patrón definido en el segundo parámetro.

Antes de continuar al siguiente paso de esta entrada, deberemos anotar la ruta completa para llegar a nuestra función EmailValidator.Validators.ValidateEmail.

Vamos a compilar nuestro proyecto y deberá generar el DLL con nuestra función incluida.

Debemos validar el framework bajo el que está siendo construido nuestro DLL, si va a ser utilizado en SQL Server 2008 deberá estar compilado en el framework 2.0, en caso que vayamos a utilizarlo en SQL Server 2012 puede estar compilado en el framework 4.0

Crear ASSEMBLY en base de datos

Vamos a obtener la ruta completa para llegar al DLL que acabamos de generar y la utilizaremos para importarlo en nuestra base de datos.

Vamos a abrir un nuevo query que utilice la base de datos de usuario de tu elección, en mi caso tengo una base de datos llamada ExpertsExchange.

USE ExpertsExchange
GO
CREATE ASSEMBLY Validators
FROM 'D:\Personal\Blogger\EmailValidator\EmailValidator\bin\Debug\EmailValidator.dll'
WITH PERMISSION_SET = SAFE


Nuestro assembly se va a llamar Validators y tendrá un conjunto de permisos SAFE, ¿qué significa esto?

SAFE (valor por defecto) es el nivel más recomendado dado que no permite que la función o funciones incluidas en el assembly tengan acceso al sistema de archivos, a la red, a las variables de entorno o al registro de windows.

EXTERNAL_ACCESS le permite al assembly utilizar recursos externos como son archivos, red, variables de entorno y registro de windows.

UNSAFE le permite al assembly utilizar recursos externos tal como lo hace EXTERNAL_ACCESS y aparte le permite al assembly ejecutar código no administrado (código no hecho en .NET)

Crear función mapeada

Ahora vamos a crear una función escalar que reciba como parámetro el correo electrónico que se quiere validar y devuelva un BIT que indique si es válido o no.

La parte interesante es que sólo declararemos la firma de la función, el cuerpo del código ya está definido en nuestro assembly.

USE ExpertsExchange
GO
CREATE FUNCTION udfValidateEmail(
    @Email NVARCHAR(255)
)
RETURNS BIT
AS EXTERNAL NAME Validators.[EmailValidator.Validators].ValidateEmail


Notemos que la firma es como la de cualquier función que crearíamos en SQL Server, la diferencia radica en el código que está después de la especificación del tipo de resultado de nuestra función.

Aun cuando un correo electrónico no lleva caracteres extraños, es imprescindible utilizar NVARCHAR dado que las cadenas que maneja .NET son UNICODE y por lo tanto SQL Server envía NVARCHAR a .NET para que éstas sean manejadas.

¿Cómo saber qué poner en external name?, es fácil:

SQLAssemblyName.[FullNamespace.Class].NETFunctionName


Ahora vamos a probar nuestra función con algunos correos electrónicos tanto válidos como no válidos:

USE ExpertsExchange
GO
SELECT dbo.udfValidateEmail(N'valid@mail.com')
SELECT dbo.udfValidateEmail(N'invalid@mail..com')
SELECT dbo.udfValidateEmail(N'invalid@mail')
SELECT dbo.udfValidateEmail(N'valid@mail.com.mx')


Si recibes un mensaje de error indicando que el .NET Framework está deshabilitado es porque SQL Server no acepta la ejecución de código CLR externo por defecto.    Vamos a habilitarlo con el siguiente código:

sp_configure 'clr enabled', 1
GO
RECONFIGURE


Vuelve a probar la función y verás que el primer y cuarto correos son válidos y el segundo y tercero no lo son.

Espero te haya resultado de utilidad esta entrada, la integración de funciones CLR a SQL Server es un tema apasionante debido a la gran diversidad de soluciones que se pueden implementar via .NET, pero ten mucho cuidado, esta no es la solución a todos nuestros problemas, necesitamos hacer pruebas que demuestren que el uso de recursos y la velocidad de respuesta son las deseadas.

2 comentarios:

  1. Buenas tardes, sé que ha pasado mucho tiempo de este post pero estoy intentado hacer lo que me indica y tengo un error creando el Assembly: Assembly 'ClassLibrary2' references assembly 'system.runtime, version=4.0.20.0, culture=neutral, publickeytoken=b03f5f7f11d50a3a.', which is not present in the current database. SQL Server attempted to locate and automatically load the referenced assembly from the same location where referring assembly came from, but that operation has failed (reason: 2(El sistema no puede encontrar el archivo especificado.)). Please load the referenced assembly into the current database and retry your request.
    Quizá podría ayudarme o hacer un tutorial más detallado o incluso un vídeo.
    Gracias

    ResponderBorrar
    Respuestas
    1. Buenas tardes Alexandra, una disculpa por la enorme tardanza :s, el trabajo me distrajo de mi blog y no he podido dedicarle el tiempo que me gustaría.
      Al parecer tu ensamblado lo estás haciendo en una versión posterior del framework, te recomiendo utilizar la versión 2.0 para que no tengas problemas con las referencias.

      Borrar