Consultando RDF con SPARQL en C# (2da parte)

En la primera parte de este artículo describí cómo mostrar los resultados de una consulta SPARQL en un DataGridView, transformando los datos originales en formato SPARQL/XML a un Dataset.  Ahora describiré un segundo método en el cual DataSource es una lista genérica de tipo “desconocido” en tiempo de compilación.  En cada ejecución de la consulta se construirá una nueva clase cuyas propiedades se nombrarán según los nombres de las variables de la consulta.

Utilizando clases dinámicas como DataSource

En este caso no usaré los resultados en formato SPARQL/XML (la clase SparqlXmlQuerySink de la biblioteca SemWeb).  En su lugar crearé mi propia clase heredando de la clase abstracta QueryResultSink, lo que permitirá prescindir del XML y construir un modelo de objetos en memoria a la medida de la estructura de los resultados obtenidos, el cual será asignado directamente al DataSource del DataGridView.  El motor SPARQL de SemWeb requiere una instancia de una clase derivada de QueryResultSink para depositar los resultados.  Ello posibilita máxima flexibilidad para su almacenamiento en cualquier formato según las necesidades de la aplicación.  Solo se requiere escribir una implementación del método abstracto Add, que es llamado ante cada conjunto de asignaciones de variable; aquí definimos qué hacer con cada resultado.  Para nuestro propósito, Add sencillamente creará un nuevo objeto de una clase que por ahora llamaré X, asignará a las propiedades de X (que se nombran igual que las variables) los valores obtenidos de la ejecución consulta SPARQL, y por último añadirá el objeto a una lista genérica de tipo List<X>

La verdadera complicación de este enfoque radica en cómo construir en C# la clase X, la cual probablemente tendrá una signatura diferente durante cada invocación de consultas diferentes, correspondiente al número y nombres de las variables de la consulta.  El problema es que C# no permite (al menos de forma trivial) crear una clase y definir sus propiedades en tiempo de ejecución.  Afortunadamente existe System.Reflection que permite sortear este impedimento.  Para mayor fortuna, conjuntamente con la versión CTP de Visual Studio 2008, se publicó un grupo de ejemplos de LINQ, disponibles desde el blog de Charlie Calvert de Microsoft, uno de los cuales trata sobre como construir consultas dinámicas en LINQ.  Para no complicar las cosas más de la cuenta, aquí solo usaré de estos ejemplos el archivo “dynamic.cs” que contiene el método CreateClass de la clase DynamicExpression, el cual precisamente, a partir de un arreglo de propiedades dinámicas (Nombre de Propiedad, Tipo de datos) crea una nueva clase.

Armados con todas las herramientas y volviendo al asunto original, ya estamos listos para crear la clase DynamicObjectsResultSink que almacenará los resultados de nuestras consultas SPARQL, accesibles a través de la propiedad pública queryResults de tipo List<X>.

    public class DynamicObjectsResultSink : QueryResultSink
    {
        public List<object> queryResults;
        Type claseDinamica;

        public override void Init(Variable[] variables)
        {
            base.Init(variables);

            DynamicProperty[] properties = new DynamicProperty[variables.Length];
            for (int i = 0; i < variables.Length; i++)
                properties&#91;i&#93; = new DynamicProperty(variables&#91;i&#93;.LocalName, typeof(string));

            claseDinamica = DynamicExpression.CreateClass(properties);

            // Crear un objeto 'modelo'
            object obj = Activator.CreateInstance(claseDinamica);
            for (int i = 0; i < variables.Length; i++)
                claseDinamica.GetProperty(variables&#91;i&#93;.LocalName).SetValue(obj, "", null);

            // Crear la lista generica del tipo del objeto modelo
            queryResults = Helper.MakeList(obj);

            // Vaciar la lista, dejarla preparada para recibir los objetos reales
            queryResults.Clear();
        }

        public override bool Add(VariableBindings result)
        {
            object obj = Activator.CreateInstance(claseDinamica);

            for (int i = 0; i < result.Variables.Length; i++)
                claseDinamica.GetProperty(result.Variables&#91;i&#93;.LocalName).SetValue(obj, result&#91;result.Variables&#91;i&#93;&#93;.ToString(), null);

            queryResults.Add(obj);

            return true;
        }
    }
&#91;/sourcecode&#93;

Todo el código de creación de la clase y la lista ocurre dentro del método Init.  Ojo con la creación de la lista: como el compilador no acepta crear una lista genérica de un tipo dinámico (miLista = new List&lt;X&gt;),  nos obliga a recurrir a un artilugio a través de la función MakeList(object) que tomé prestada del blog de <a href="http://kirillosenkov.blogspot.com/2008/01/how-to-create-generic-list-of-anonymous.html" target="_blank">Kirill Osenkov</a>, la cual simplemente lo "engaña", haciéndole "creer" que el objeto que le estamos pasando pertenece a una clase "normal" no dinámica.

        public static List<T> MakeList<T>(T itemOftype)
        {
            List<T> newList = new List<T>();
            newList.Add(itemOftype);
            return newList;
        }

Luego solo queda realizar la ejecución de la consulta de forma que vierta los resultados en un objeto de tipo DynamicObjectsResultSink, y utilizar la propiedad queryResults como DataSource del DataGridView:

        public void QueryDataSourceDynamicClass(string url, string consulta)
        {
            try
            {
                SparqlHttpSource almacen = new SparqlHttpSource(url);
                SparqlEngine query = new SparqlEngine(consulta);
                DynamicObjectsResultSink results = new DynamicObjectsResultSink();
                query.Run(almacen, results);
                gridResults.DataSource = results.queryResults;
            }
            catch (Exception ex)
            {
                MessageBox.Show("Error procesando query: " + ex.Message);
            }
        }

Consideraciones finales

Al menos en teoría, este método debe resultar más eficiente que el primero descrito, pues elimina la necesidad de la transformación del resultado en SPARQL/XML a otro XML más “amistoso” con Dataset.ReadXml;  además evita la sobrecarga que implica la construcción de un Dataset.  Sin embargo, evidentemente resulta mucho más complicado de programar aún cuando se ha usado la función CreateClass, pero de todas formas la complejidad se mantiene en dynamic.cs.  Recomiendo consultar el código que implementa esta función para comprobar el extenso uso de Reflection.  Otro aspecto poco “natural” resulta el truco para crear la lista genérica de tipo anónimo; como truco resulta instructivo y hasta elegante, pero me hace sentir como que al lenguaje le falta “algo”.

En un final, si el objetivo requiere la complejidad, se puede aceptar, pero en mi opinión esta versión tiene un problema más grave si se fuera a usar en un sistema más grande, y quizás alguien con más experiencia en el tema del CLR nos pueda dejar algún comentario aclaratorio.  Hasta donde conozco, .NET no ofrece ningún mecanismo para eliminar definiciones de clases, y estoy creando una clase nueva ante cada ejecución de la consulta.  Qué sucedería si emplearamos este método en un servicio que ejecute ininterrumpidamente diferentes consultas, miles de consultas?  Saturarían estas definiciones de clases la memoria?

Creo haber probado que es perfectamente posible en C# (y con SemWeb, por supuesto) consultar RDF a través del lenguaje SPARQL y manipular los resultados, aunque no siempre de la forma más intuitiva posible. He presentado dos métodos alternativos e intentado analizar sus pros y contras, escoja usted el de su preferencia y si le ha sido útil este trabajo, o desea criticarlo o ampliarlo, por favor, déjeme una nota.

Anuncios

One Response to Consultando RDF con SPARQL en C# (2da parte)

  1. Pingback: .NET 4 y Web Semántica: el inicio de un romance « El Barco Tecnológico

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: