lunes, 29 de octubre de 2018

Leyendo RSS desde SQL Server



En esta entrada quiero abordar una forma en la que podemos obtener información de un RSS e integrarla a una base de datos.

Es común que haya información que resulte relevante o interesante para la operación de una empresa y ésta se encuentre disponible a través de fuentes RSS, el cual es un formato bien definido a través de un esquema XML que podemos encontrar en RSS 2.0 at Harvard Law.

Elegí esa dirección para mostrar el esquema del documento porque siento que la documentación ahí mostrada está ordenada de una forma muy sencilla de entender.

Para implementar una solución que nos permita obtener fuentes RSS necesitamos de un módulo que lea la información de la URL donde está ubicado el XML y posteriormente se lo dé a SQL Server para que tome el XML y lo convierta a una representación entidad-relación que es mucho más manejable.

Este proyecto lo dividiremos en los siguientes pasos:

  1. Tomar una fuente RSS como muestra.
  2. Crear un módulo en .NET que pueda ser integrado a SQL Server.
  3. Crear un assembly en SQL Server y crear la función que devuelva la información contenida en la URL que reciba como parámetro.

URL de muestra

Tomaré como base el RSS de deportes del sitio web de ESPN, si abrimos la liga nos mostrará un XML con el contenido de la fuente.

Módulo de .NET

Voy a crear un proyecto del tipo Biblioteca de Clases en Visual Studio 2017 y lo llamaré SQLRSSReader, en él crearemos un par de clases:

  1. RSSItem para representar cada entrada de la fuente RSS.
  2. Lector para implementar la función que SQL Server llamará.

A continuación el código de RSSItem:

namespace SQLRSSReader
{
    public class RSSItem
    {
        public string Title { get; set; }
        public string Description { get; set; }
        public string Link { get; set; }
        public string PubDate { get; set; }
    }
}

Para exponer una función tabular a SQL Server, es necesario que se den las siguientes condiciones:

  1. Tener el atributo SqlFunctionAttribute.
  2. Pública y estática.
  3. Devolver IEnumerable.
  4. Los tipos de parámetros de entrada deben ser de SQL Server.

La función quedaría de la siguiente manera:

[SqlFunction(FillRowMethodName = "LlenarItem")]
public static IEnumerable ObtenerRSS(SqlString url)
{
    HttpWebRequest webRequest = HttpWebRequest.Create(url.Value) as HttpWebRequest;
    HttpWebResponse webResponse = webRequest.GetResponse() as HttpWebResponse;
    List<RSSItem> result = null;

    using (StreamReader sr = new StreamReader(webResponse.GetResponseStream()))
    {
        XmlDocument xdRSS = new XmlDocument();
        xdRSS.LoadXml(sr.ReadToEnd());
        result = new List<RSSItem>();
        foreach (XmlNode xnItem in xdRSS.SelectNodes("/rss/channel/item"))
        {
            result.Add(new RSSItem()
            {
                Description = (xnItem.SelectSingleNode("./description") != null ? xnItem.SelectSingleNode("./description").InnerText : null),
                Link = (xnItem.SelectSingleNode("./link") != null ? xnItem.SelectSingleNode("./link").InnerText : null),
                PubDate = (xnItem.SelectSingleNode("./pubDate") != null ? xnItem.SelectSingleNode("./pubDate").InnerText : null),
                Title = (xnItem.SelectSingleNode("./title") != null ? xnItem.SelectSingleNode("./title").InnerText : null)
            });
        }
    }

    return result;
}

Lo que hace nuestra función es descargar el RSS, llenar una lista de objetos RSSItem y devolverla como resultado.

Notarás que el atributo SqlFunction tiene un parámetro llamado FillRowMethodName, en él se coloca el nombre de la función que se mandará a llamar para llenar la tabla resultante.    Por cada objeto que contenga IEnumerable se llamará a la función a la que apunta FillRowMethodName.

Ahora veamos la función LlenarItem:

public static void LlenarItem(object rssItemObject, out SqlString Title, out SqlString Description, out SqlString Link, out SqlString PubDate)
{
    RSSItem rssItem = rssItemObject as RSSItem;
    if (rssItem.Title != null)
        Title = new SqlString(rssItem.Title);
    else
        Title = SqlString.Null;
    if (rssItem.Description != null)
        Description = new SqlString(rssItem.Description);
    else
        Description = SqlString.Null;
    if (rssItem.Link != null)
        Link = new SqlString(rssItem.Link);
    else
        Link = SqlString.Null;
    if (rssItem.PubDate != null)
        PubDate = new SqlString(rssItem.PubDate);
    else
        PubDate = SqlString.Null;
}

Vamos a analizar los parámetros uno por uno:

  1. rssItemObject.   Es donde recibiremos la instancia de RSSItem que se quiere operar en ese momento.
  2. Title.    Primera columna de la tabla resultante.
  3. Description.    Segunda columna de la tabla resultante.
  4. Link.    Tercera columna de la tabla resultante.
  5. PubDate.    Cuarta columna de la tabla resultante.

También debemos notar que cada una de las columnas tiene "out", eso nos obliga a darle un valor (aunque sea null) antes de terminar la ejecución de la función.

Compilemos nuestro proyecto para generar el DLL correspondiente.

Función en SQL Server

Para probar nuestra función vamos a crear una base de datos llamada SQLBlog y le habilitaremos la opción TrustWorthy para que se le considere una base de datos de confianza.

USE master
GO
IF EXISTS (SELECT name FROM sys.databases WHERE name = N'SQLBlog')
BEGIN
ALTER DATABASE SQLBlog
    SET SINGLE_USER
    WITH ROLLBACK IMMEDIATE;
    DROP DATABASE SQLBlog
END
GO
CREATE DATABASE SQLBlog
ON PRIMARY (
    NAME = 'SQLBlog_dat',
    FILENAME = 'C:\LaCarpeta\SQLBlog.mdf'
)
LOG ON (
    NAME = 'SQLBlog_log',
    FILENAME = 'C:\LaCarpeta\SQLBlog.ldf'
)
GO
ALTER DATABASE SQLBlog
SET TRUSTWORTHY ON

Ahora vamos a crear el ensamblado que alojará el DLL que acabamos de crear en .NET:

USE SQLBlog
GO
CREATE ASSEMBLY SQLRSSReader
FROM 'C:\ElEnsamblado\SQLRSSReader.dll'
WITH PERMISSION_SET = UNSAFE

Finalmente creamos la función y la probamos:

CREATE FUNCTION udfGetRSS(
    @URL NVARCHAR(256)
)
RETURNS TABLE(
    Title NVARCHAR(200),
    Description NVARCHAR(4000),
    Link NVARCHAR(256),
    PubDate NVARCHAR(50)
)
AS EXTERNAL NAME SQLRSSReader.[SQLRSSReader.Lector].ObtenerRSS
GO
SELECT * FROM udfGetRSS(N'http://www.espn.com/espn/rss/news')

Antes de terminar con esta entrada quiero que notes lo siguiente:

  1. El ensamblado debe ser creado con PERMISSION_SET en UNSAFE porque va a realizar lectura de sitios web, es decir que necesita salir del servidor.
  2. Las cadenas expuestas por la función deben ser del tipo UNICODE, por ello notarás que son NVARCHAR.
  3. La notación para apuntar a la función es la siguiente: AssemblyName.[Namespace.Class].Function

Prueba con otra dirección, por ejemplo: https://www.tendencias21.net/xml/syndication.rss

Espero te haya resultado de utilidad esta entrada, ¡saludos!



No hay comentarios.:

Publicar un comentario