RSS

Mono + NHibernate + SQLite

martes, 4 de noviembre de 2008


Después de usar por tanto tiempo Gentle.NET, un ORM que ha sido discontinuado, he decidido al fin comenzar a estudiar NHibernate. Los ejemplos que dan en los manuales y páginas son para Windows (con MS .NET) y SQL Server. Trabajando en GNU/Linux, hice las pruebas con SQLite.

El problema es que el driver utilizado por NHibernate para esta base de datos utiliza un binding desactualizado. Además Mono trae una implementación mejor, que soporta el standard ADO.NET 2.0, que es Mono.Data.Sqlite, disponible a partir de la versión 1.2.4.

El post es especialmente útil para aquellos que quieran utilizar en GNU/Linux (yo uso Ubuntu Gutsy), con Mono, NHibernate y SQLite, utilizando el binding Mono.Data.Sqlite. Si bien puede parecer esto tan fácil como seguir el documento QuickStart y cambiar las opciones correspondientes, no lo es si se intenta utilizar SQLite. Por eso, en este post no voy a explicar todas las cosas, ya que no me interesa y se pueden aprender en la documentación, sino que voy a desarrollar un ejemplo muy sencillo y mostrar cómo utilizar SQLite, ya que hay que solucionar unos problemas no muy triviales.

Cabe aclarar que, si bien todavía no lo he probado, los archivos de mapeo (hbm.xml) y el schema SQL se pueden autogenerar.

Primero creo la tabla que almacenerá los objetos en un archivo llamado, por ejemplo, data.db.sql:

DROP TABLE IF EXISTS personas;

CREATE TABLE personas (
id integer PRIMARY KEY,
nombre varchar(20),
apellido varchar(20)
);

Para el ejemplo utilicé la última versión de SQLite. Para crear la base de datos en el archivo data.db, ejecutamos:

$ sqlite3 data.db <>

La clase de ejemplo (Persona.cs):

using System;

namespace PruebaNHibernate
{
public class Persona
{
private int id;
private string nombre;
private string apellido;

public Persona()
{
}

public int Id {
get { return id; }
set { this.id = value; }
}

public string Nombre {
get { return nombre; }
set { this.nombre = value; }
}

public string Apellido { get { return apellido; } set { this.apellido = value; } } } }

Luego hay que escribir el archivo de mapeo, que es un XML.
version="1.0" encoding="utf-8" ?>
xmlns="urn:nhibernate-mapping-2.2"
namespace="PruebaNHibernate" assembly="PruebaNHibernate">


name="Persona" table="personas">
name="Id" column="id" type="Int32">
class="increment" />
>

name="Nombre" column="nombre" />
name="Apellido" column="apellido" />
>
>

Este archivo se debe agregar como recurso en MonoDevelop.

Luego escribimos el archivo de configuración para nuestra aplicación (app.config):

<?xml version="1.0" encoding="utf-8" ?>
>
>
name="nhibernate"
type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b77a5c561934e089" />

>

>

key="hibernate.connection.provider"
value="NHibernate.Connection.DriverConnectionProvider"
/>


key="hibernate.dialect"
value="NHibernate.Dialect.SQLiteDialect"
/>


key="hibernate.connection.driver_class"
value="PruebaNHibernate.MonoDataSqliteDriver, PruebaNHibernate"
/>


key="hibernate.connection.connection_string"
value="Data Source=data.db,version=3"
/>


key="hibernate.query.substitutions"
value="true=1;false=0"
/>


key="hibernate.use_prox y_validator"
value="false"
/>

>
>

Nota: Debido a problemas con el hosting no puedo colocar, arriba, el string correcto en la última parte. La palabra prohibida “p r o x y” (sin espacios) tiene un espacio de más entre la “x” y la “y”. En fin, deshabilito en esas lineas la comprobación de clases p r o x y.

Luego en un archivo como Main.cs agrego código en el método principal Main para jugar con todo esto:

using System;
using NHibernate;
using NHibernate.Cfg;

namespace PruebaNHibernate
{
class MainClass
{
public static void Main (string[] args)
{
Configuration conf = new Configuration();
/* Con la siguiente línea leo todo los archivos con extensión
* hbm.xml embebidos como recurso en el assembly especificado */

conf.AddAssembly("PruebaNHibernate");

ISessionFactory sessionFactory = conf.BuildSessionFactory();

ISession s = sessionFactory.OpenSession();

ITransaction tx = s.BeginTransaction();

Persona p = new Persona();
p.Nombre = "Milton";
p.Apellido = "Pividori";
s.Save(p);
tx.Commit();

Console.WriteLine("p - Id: " + p.Id);
Console.WriteLine("p - Nombre: " + p.Nombre);
Console.WriteLine("p - Apellido: " + p.Apellido);
Console.WriteLine();

Persona p2 = (Persona)s.Load(typeof(Persona), p.Id);

Console.WriteLine("p2 - Id: " + p2.Id);
Console.WriteLine("p2 - Nombre: " + p2.Nombre);
Console.WriteLine("p2 - Apellido: " + p2.Apellido);
Console.WriteLine();

/* Verifico si son el mismo objeto p y p2, ya que estamos
* dentro de la misma sesión */

if (p == p2)
Console.WriteLine("p y p2 son el mismo objeto");

p2.Apellido = "Paduán";

s.Flush();

s.Close();
}
}
}

En el proyecto agregué estas referencias:

  • NHibernate.dll
  • System

Ahora vamos a lo que nos interesa. En el archivo app.config (que al compilar se traducirá a .exe.config en el directorio de salida), observemos estas lineas:


key="hibernate.connection.driver_class"
value="PruebaNHibernate.MonoDataSqliteDriver, PruebaNHibernate"
/>

NHibernate trae un driver para SQLite, como dije al principio, pero que utiliza un provider que fue discontinuado. Por lo tanto, para utilizar Mono.Data.Sqlite, cree una clase en mi assembly (PruebaNHibernate) con el nombre que se indica allí (MonoDataSqliteDriver):

using System;
using NHibernate.Driver;

namespace PruebaNHibernate
{
public class MonoDataSqliteDriver : ReflectionBasedDriver
{
public MonoDataSqliteDriver()
: base("Mono.Data.Sqlite",
"Mono.Data.Sqlite.SqliteConnection",
"Mono.Data.Sqlite.SqliteCommand")
{
}

public override bool UseNamedPrefixInSql
{
get { return true; }
}

public override bool UseNamedPrefixInParameter
{
get { return true; }
}

public override string NamedPrefix
{
get { return "@"; }
}

public override bool SupportsMultipleOpenReaders
{
get { return false; }
}
}
}

En el constructor de la misma le paso a mi clase padre los argumentos que necesita: nombre del assembly (en este caso “Mono.Data.Sqlite”), clase para crear la conexión y ejecutar comandos. Desconozco los detalles de SQLite, así que copié los métodos sobreescritos del driver viejo.

No pude hacer que NHibernate cargue el assembly “Mono.Data.Sqlite” desde el GAC, así que hay que copiarlo al directorio del ejecutable. Es necesario copiar otros archivos, como las dependencias de NHibernate y la base de datos (data.db). Se pueden bajar el proyecto completo para MonoDevelop aquí. El archivo obviamente no es un png, pero sino no lo puedo subir :)

Ahora bien, hay un problemita… el archivo System.Data.dll que viene con Mono 1.2.4 en Gutsy tiene un bug que hará que el ejemplo no funcione. El mismo ya fue solucionado en la versión 1.2.6. Así que, como workaround, pueden bajarse el paquete de Debian libmono-system-data2.0-cil correspondiente a la versión 1.2.6 de Mono y sobreeescribir ese archivo, que se ubica en /usr/lib/mono/gac/System.Data/2.0.0.0__b77a5c561934e089/. Reporté el problema en Ubuntu, así que quizá lo actualicen dentro de poco.

Con esto podremos utilizar SQLite con NHibernate, bajo Mono, con un binding actualizado.

Autor : Milton Pividori

FUENTE DEL ARTICULO : http://www.miltonpividori.com.ar/2007/12/30/mono-nhibernate-sqlite/