Consultando RDF con SPARQL en C# (1ra parte)

En enero de 2008, el W3C publicó con carácter de Recomendación las especificaciones del Lenguaje de Consulta SPARQL para RDF, dotando de esta forma al versátil modelo de representación de una poderosa herramienta de consulta distribuída. Muchos aseveran que SPARQL es para RDF lo que SQL es para las bases de datos relacionales: el estándar de consulta. La especificación actual no contiene todavía mecanismos para inserción/actualización/eliminación pero se anuncia su pronta incorporación. De cualquier forma, ya es fácil encontrar en la red numerosos proyectos que exponen sus datos en forma de puntos de acceso (endpoints) SPARQL a través de HTTP, luego resulta interesante jugar un poco con estos sitios, ejecutar consultas sobre ellos y visualizar los resultados.  Con esto vamos adquiriendo “visión” sobre como emplear estas novedosas tecnologias en nuestras aplicaciones.

Como parte del trabajo en Infocam, he estado creando una herramienta interactiva muy sencilla para experimentar con archivos RDF en formato RDF/XML, N3 y puntos de acceso SPARQL.  En este articulo muestro dos formas alternativas de visualizar los resultados de una consulta SELECT SPARQL en un DataGridView (o GridView en ASP.NET).  Al mismo tiempo, los tropiezos recibidos me sirven de pretexto para dar unas vueltas a las limitaciones de la implementación actual de C# (3.0) para el trabajo con RDF y la web semántica en general.  Para ambos métodos uso la biblioteca SemWeb de Joshua Tauberer.

A modo de ejemplo emplearé el punto de acceso SPARQL (http://www4.wiwiss.fu-berlin.de/dblp/sparql) a la base de datos bibliográfica DBLP de la universidad alemana de Trier.  Esta base contiene referencias bibliográficas de más de un millón de artículos y proceedings de las revistas más importantes sobre Ciencia de la Computación.  Si deseamos recuperar cinco artículos cualesquiera, enviamos la consulta:

SELECT DISTINCT ?articulo ?revista
WHERE { ?articulo <http://www4.wiwiss.fu-berlin.de/dblp/terms.rdf#journal> ?revista }
limit 5 offset 0

De la ejecución de una consulta SELECT en SPARQL se obtiene un conjunto de resultados o asignaciones de las variables involucradas en dicha consulta.  En el ejemplo anterior las variables son ?articulo y ?revista (los nombres de variables comienzan con signo de interrogación).

Se desea mostrar los resultados en un DataGridView de forma que cada fila represente una asignación de variables específica y cada columna represente una variable. Ello requiere la preparación de un objeto adecuado para asignar a la propiedad DataSource.  Este objeto puede ser un Dataset o una colección de objetos personalizados.  A continuación examino ambas variantes.

Utilizando un Dataset como DataSource

SemWeb ofrece la clase SparqlEngine que permite ejecutar una consulta SPARQL sobre un almacén RDF.  Dicho sea de paso, de forma muy elegante encapsula los detalles, de forma que puede ser un almacén remoto como un SparqlHttpSource (endpoint) o un almacén local (MemoryStore), a pesar que el último no soporta de forma nativa el servicio de ser consultado con SPARQL.  Bravo por Josh Tauberer !!!   Por si fuera poco ofrece además una clase SparqlXmlQuerySink donde deposita los resultados de la consulta en SPARQL/XML, un formato XML estándar creado por el W3C a estos efectos.

La consulta que estamos empleando a modo de ejemplo sobre el punto de acceso de la base DBLP devuelve el siguiente XML:

<sparql xmlns="http://www.w3.org/2005/sparql-results#">
  <head>
    <variable name="revista" />
    <variable name="articulo" />
  </head>
  <results>
    <result>
      <binding name="revista">
	<literal>Advances in Computers</literal>
      </binding>
      <binding name="articulo">
        <uri>http://www4.wiwiss.fu-berlin.de/dblp/resource/record/journals/ac/KandelS89</uri>
      </binding>
    </result>
    <result>
      <binding name="revista">
	<literal>Advances in Computers</literal>
      </binding>
      <binding name="articulo">
        <uri>http://www4.wiwiss.fu-berlin.de/dblp/resource/record/journals/ac/Dasgupta82</uri>
      </binding>
    </result>
    <result>
      <binding name="revista">
	<literal>Advances in Computers</literal>
      </binding>
      <binding name="articulo">
        <uri>http://www4.wiwiss.fu-berlin.de/dblp/resource/record/journals/ac/Dasgupta89</uri>
      </binding>
    </result>
    <result>
      <binding name="revista">
	<literal>Advances in Computers</literal>
      </binding>
      <binding name="articulo">
        <uri>http://www4.wiwiss.fu-berlin.de/dblp/resource/record/journals/ac/DasguptaS85</uri>
      </binding>
    </result>
    <result>
      <binding name="revista">
	<literal>Advances in Computers</literal>
      </binding>
      <binding name="articulo">
        <uri>http://www4.wiwiss.fu-berlin.de/dblp/resource/record/journals/ac/Wexelblat75</uri>
      </binding>
    </result>
  </results>
</sparql>

Ahora debemos convertir los resultados en este formato a un DataTable que será empleado como DataSource del DataGridView.  Afortunadamente el Dataset expone el método ReadXml que permite la importación de datos de un documento XML. De forma general, este método lee cualquier XML y genera un conjunto de tablas (DataTable) relacionadas, a partir de la estructura del documento.  Por ello, es necesario modificar un poco la estructura de estos resultados a un formato un poco más “amistoso” de forma que ReadXml genere una sola tabla “result”, con los campos nombrados según las variables de la consulta, que nos sirva directamente de fuente de datos de nuestro DataGridView.

<?xml version="1.0" encoding="utf-16"?>
<results>
  <result>
    <revista>Advances in Computers</revista>
    <articulo>http://www4.wiwiss.fu-berlin.de/dblp/resource/record/journals/ac/KandelS89</articulo>
  </result>
  <result>
    <revista>Advances in Computers</revista>
    <articulo>http://www4.wiwiss.fu-berlin.de/dblp/resource/record/journals/ac/Dasgupta82</articulo>
  </result>
  <result>
    <revista>Advances in Computers</revista>
    <articulo>http://www4.wiwiss.fu-berlin.de/dblp/resource/record/journals/ac/Dasgupta89</articulo>
  </result>
  <result>
    <revista>Advances in Computers</revista>
    <articulo>http://www4.wiwiss.fu-berlin.de/dblp/resource/record/journals/ac/DasguptaS85</articulo>
  </result>
  <result>
    <revista>Advances in Computers</revista>
    <articulo>http://www4.wiwiss.fu-berlin.de/dblp/resource/record/journals/ac/Wexelblat75</articulo>
  </result>
</results>

Esta transformación se logra fácilmente con una sencilla expresión Linq to XML. A continuación reproduzco el código del método que realiza el proceso completo.

        public void ConsultarSparqlRemoto(string url, string consulta)
        {
            try
            {
                SparqlHttpSource almacen = new SparqlHttpSource(url);
                System.IO.StringWriter cadena = new System.IO.StringWriter();
                SparqlXmlQuerySink resultados = new SparqlXmlQuerySink(cadena);

                almacen.RunSparqlQuery(consulta, resultados);

                // Transformar el XML
                XElement root = XElement.Load(new System.IO.StringReader(cadena.ToString()));
                XNamespace xmlns = root.GetDefaultNamespace();
                var listaResultados = root.Descendants(xmlns + "result");
                var grupo = listaResultados.Select(
                    r => new XElement("result",
                        r.Elements(xmlns + "binding").Select(
                            binding => new XElement(binding.Attribute("name").Value,
                                binding.Descendants().Select(
                                    valor => (valor.Name == "uri" ? "(" + valor.Value + ")" :
                                             (valor.Name == "literal" ? "\"" + valor.Value + "\"" :
                                                valor.Value))
                            )))));
                var resultado = new XElement("results", grupo);

                // Almacenarlo para el proximo proceso
                StringWriter cadena1 = new StringWriter();
                resultado.Save(cadena1);

                //  Importarlo en el Dataset y ponerlo en el grid
                DataSet ds = new DataSet();
                ds.ReadXml(new StringReader(cadena1.ToString()));
                gridResults.DataSource = ds.Tables[0].DefaultView;
            }
            catch (Exception ex)
            {
                MessageBox.Show("Error procesando consulta: " + ex.Message);
            }
        }

De esta forma obtenemos el resultado deseado:

Herramientas Web Semántica

Si bien el proceso descrito resulta relativamente sencillo, no me convence al menos en un par de aspectos.  Primero, considero un poco forzado terminar recurriendo a un Dataset para mostrar un XML; en muchos artículos se advierte la sobrecarga incurrida al trabajar con Datasets.  Aunque en la gran mayoría de los artículos que he leído se reconoce el ReadXml como la forma estándar para visualizar XML en un grid, y aunque el Dataset se puede considerar una estructura de datos general, no puedo dejar de asociarlo al modelo relacional.  Al final las bases de datos y XML son dos modelos de datos diferentes entre los cuales existe una discrepancia similar a la clásica objeto-relación.

Segundo aspecto, LINQ brinda infinidad de posibilidades, permite virar patas arriba un XML, transformarlo totalmente, convertir elementos en atributos y viceversa, incluso partir de un modelo XML y obtener un conjunto de objetos en memoria, pero … ello me indujo a pensar cuando comencé a trabajar en esta herramienta, que usando LINQ podia obtener directamente una lista genérica de las asignaciones de variables de forma dinámica, o sea, que si las variables fueran ?revista, ?articulo y ?autor, pudiera generar un tipo anónimo, usando algo como:

Select new { binding.Attribute(“name”).Value = binding.Element(“uri”).Value }

y obtener una clase anónima cuyos atributos fueran precisamente las variables de la consulta SPARQL:

class NombreGeneradoPorElCompilador
{
  public string revista {get; set;}
  public string articulo {get; set;}
  public strint autor {get; set;}
}

Pues … craso error !!!  C# NO ADMITE expresiones variables en la definición de una propiedad de un tipo anónimo.  Profundizando un poco, es totalmente lógico, por muy dinámicas que luzcan las construcciones de C# asociadas a las nuevas características incluídas en la versión 3, tipos anónimos, propiedades auto generadas, etc, al final todo se traduce en más trabajo para el compilador, y valga la redundancia, en tiempo de compilación y sobre un modelo de tipos estático.  El compilador no puede generar una propiedad sobre la base de una expresión variable sencillamente porque el valor de la expresión NO EXISTE en tiempo de compilación.

Dicho esto, en la segunda parte de este artículo describiré un método alternativo de mostrar en un DataGridView los resultados de una consulta SPARQL, sorteando de forma algo truculenta algunas de las limitaciones aquí expuestas.  Con este método evitaré el uso del Dataset y emplearé colecciones de objetos pertenecientes a clases generadas dinámicamente.

Anuncios

4 Responses to Consultando RDF con SPARQL en C# (1ra parte)

  1. belen says:

    Hola, he leido tu articulo, me parece muy interesante. Debo procesar los resultados de una consulta sparql (con semweb) y mostrarlo en un control de un web form, pero el codigo tuyo me tira error al declarar . ¿tienes alguna idea de como hacerlo para la web?
    muchas gracias

    • johnbarquin says:

      Belen, el código funciona igual para un web form, excepto quizás las pequeñas diferencias entre controles web y desktop. Especifica que url y consulta estás usando para probar y que error te da para poder ayudarte.

      Salu2, John

  2. fernando says:

    bueno, me parece un buen aporte y el codigo tambien es muy bueno. Los lenguajes para documentos RDF son muchos. aqui tambien puedes encontrar alguna referencia. http://www.cursowebs.com/posts/view/29.
    Saludos…

  3. 4ra says:

    Hola,

    soy nueva en esto de las ontologías y el sparql. Estoy haciendo unas pruebas con el código que has descrito y con una ontología que he creado de prueba pero me salta una excepción a la hora de ejecutarlo. La excepción es la siguiente:

    “Error procesando consulta: The result of the query was not a SPARQL Results document”

    Sin embargo si hago la prueba con la consulta de ejemplo que has utilizado tu, me funciona correctamente.

    ¿A qué puede ser debido?

    Un saludo y gracias.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: