[Windows 8] Bases de datos locales en aplicaciones metro (I)

Hola a todos!

Hoy vamos a ver como está de resuelto el escenario de bases de datos locales en las aplicaciones metro con Windows 8. En Windows Phone disponíamos “out of the box” de SQL Server CE pero en Windows 8 no tenemos esta suerte. Ahora mismo hay dos opciones principales, cada una con sus pros y sus contras: SQLite y SiaqoDb. En este artículo no voy a entrar a consideraciones de rendimiento. Voy a introducir cada base de datos, ver como poder empezar a desarrollar con ella y luego analizar lo bueno y lo malo de lo que hayamos visto.  Tenemos que recordar que estamos ante un sistema operativo y un SDK Beta, por lo que no podemos pedirle peras al olmo y existen fallos que, espero y confío, se subsanarán en una futura versión RTM.

Dicho esto, empecemos con el primer participante, SQLite

SQLite

SQLite Logo

Con la versión 3.7.13 SQLite añadió soporte oficialmente para WinRT y las aplicaciones metro, el objetivo de este artículo es dar los primeros pasos para ver como usar bases de datos locales en nuestros proyectos Metro.

Consideraciones

 

 

 

SQLite es un componente nativo, no un ensamblado manejado de WinRT, esto nos obligará a tener que distribuir diferentes paquetes de nuestra aplicación, pues necesitaremos una dll de SQLite para ARM, otra para x64 y otra para x86. Actualmente, en la web de descargas de SQLite solo tenemos acceso a la dll de x64, si necesitamos la de ARM o la de x86 tendremos que descargarnos el código fuente y compilarla nosotros mismos, esto por si solo daría para varios largos artículos, por lo que os dejo un vídeo de Tim Heuer en el que explica muy bien como hacerlo. Una vez dicho esto, podemos empezar!

Setup

Por defecto SQLite no viene incluido en Windows 8 como pasa con SQL Server CE en Windows Phone, por lo que tendremos que descargarnos los componentes necesarios para hacerlo funcionar.

En primer lugar necesitas el binario de SQLite 3.7.13 que puedes descargar aquí, guárdalo en algún lugar de tu disco duro a buen recaudo, lo usaremos más adelante.

A continuación, abre Visual Studio 2012 y crea un nuevo proyecto Metro, haz click derecho sobre él en el explorador de soluciones, escoge la opción “Manage NuGet Packages…” y busca el componente sqlite-net, un ORM creado a medida para SQLite y que ya es compatible totalmente con WinRT, además de hacer uso de la TPL (Task, async, await) para trabajar con la base de datos, lo cual es un gran extra:

image

Solo tienes que hacer click en el botón install y se añadirán dos clases a tu proyecto: SQLite.cs y SQLiteAsync.cs .

Ahora solo necesitamos incluir la dll de SQlite 3 en nuestro proyecto, como se trata de un componente nativo y no de un ensamblado, no lo añadiremos como una referencia, lo añadiremos directamente como contenido en nuestro proyecto (Add > Add Existing Item):

image

Una vez añadido, establecemos su Build Action a Content y Copy To Output Directory a Copy if newer.

Ya estamos listos para trabajar con SQLite en nuestra aplicación metro!

Usando SQLite-net

Primero definamos una tabla, ¿Como? Pues de una manera conocida, usando una clase decorada con atributos y que contenga propiedades públicas que actuarán como campos de la tabla, en nuestro caso una tabla sencilla, Customers:

  1: public class Customer
  2: {
  3:     [PrimaryKey, AutoIncrement]
  4:     public int id { get; set; }
  5: 
  6:     [MaxLength(100)]
  7:     public string Name { get; set; }
  8: 
  9:     [MaxLength(256)]
 10:     public string Address { get; set; }
 11: }

Todos estos atributos los encontramos definidos en el namespace SQLite.

Ahora es momento de realizar el código necesario para crear nuestra base de datos y sus tablas, para esto haremos uso del soporte incluido en sqlite-net para el uso asíncrono de nuestra base de datos:

  1: private async void InitializeDb()
  2: {
  3:     var db = new SQLiteAsyncConnection(Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, "people.sqlite"));
  4:     var result = await db.CreateTableAsync<Customer>();
  5: }

De esta forma tan sencilla creamos la base de datos. Como se puede observar le pasamos la ruta completa donde deseamos crear la base de datos, usando el almacenamiento de datos de nuestra aplicación. En esta parte nos encontraremos con un grave problema, al menos bajo mi punto de vista: La ruta que usemos para la base de datos no puede contener carácteres no standard, como tildes, cedillas u otros por el estilo. Por ejemplo, en mi caso la ruta incluye mi nombre de usuario, que es “JosuéYeray” con tilde. al intentar crear la base de datos nos encontramos con el siguiente error:

image

Esto pinta feo, la excepción tampoco es muy descriptiva, pero después de bucear algunos días por internet, encontré la solución, usar una ruta sin tildes ni ningún otro carácter especial, con lo que me cree un usuario “Dummy” y todo funciono como la seda, pero depender de como esté escrito el nombre del usuario que ejecuta la aplicación es, cuanto menos, peligroso. ¿Podríamos usar otro path para la base de datos? En principio estamos restringidos al path de datos de nuestra aplicación que por defecto se encuentra bajo nuestro usuario. Esperemos que lo arreglen en siguientes versiones y si no, tenemos el código fuente a nuestra disposición, ¿Quién dijo miedo?

A continuación, si no hemos huido espantados con el error anterior, añadimos una tabla de tipo Customer. el resultado será el número de entradas generadas en el esquema de base de datos. ¿Qué pasa si la tabla ya existe? CreateTableAsync ejecuta internamente una operación “Create if not exist”, por lo que si la tabla ya existe no realizará ninguna acción.

Ahora ya podemos empezar a insertar registros en nuestra tabla Customer:

  1: private async void InitializeDb()
  2: {
  3:     var db = new SQLiteAsyncConnection(Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, "people.sqlite"));
  4:     var result = await db.CreateTableAsync<Customer>();
  5:                 
  6:     Customer c = new Customer();
  7:     c.Name = "Josué Yeray";
  8:     c.Address = "Bilbao";
  9: 
 10:     int insertedItems = await db.InsertAsync(c);
 11: 
 12:     if (insertedItems > 0)
 13:     {
 14:         MessageDialog mDlg = new MessageDialog(string.Format("New inserted id: {0}", c.id));
 15:         await mDlg.ShowAsync();
 16:     }
 17: }

Meridianamente simple, rellenamos una instancia de nuestra tabla Customer y llamamos al método InsertAsync, que nos devolverá el número de registros insertados.

Y por último pero no menos importante, como recuperar datos guardados, gracias a Linq, más fácil todavía:

  1: private async void InitializeDb()
  2: {
  3:     var db = new SQLiteAsyncConnection(Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, "people.sqlite"));
  4:     var result = await db.CreateTableAsync<Customer>();
  5:                 
  6:     Customer c = new Customer();
  7:     c.Name = "Josué Yeray";
  8:     c.Address = "Bilbao";
  9: 
 10:     int insertedItems = await db.InsertAsync(c);
 11: 
 12:     if (insertedItems > 0)
 13:     {
 14:         MessageDialog mDlg = new MessageDialog(string.Format("New inserted id: {0}", c.id));
 15:         await mDlg.ShowAsync();
 16: 
 17:         var customer = await db.Table<Customer>().Where(cust => cust.Name == "Josué Yeray").FirstOrDefaultAsync();
 18: 
 19:         if (customer != null)
 20:         {
 21:             mDlg.Content = string.Format("Customer Name: {0}, Address: {1}", customer.Name, customer.Address);
 22:             mDlg.ShowAsync();
 23:         }
 24:     }
 25: }

Haciendo uso de Linq podemos consultar la tabla que nos interese haciendo uso del objeto Table<T> que expone sqlite-net. También nos permite ejecutar consultas SQL si lo necesitamos:

  1: int NumCustomers = await db.ExecuteScalarAsync<int>("SELECT COUNT(Id) FROM Customer");

Pero recuerda, cada vez que usas SQL en línea en vez de usar Linq, dios mata un gatito, así que no lo uses, piensa en los gatitos.

Conclusiones

Aunque todavía tenemos que esperar a ver como se comporta SiaqoDb, hay varias cosas que no me gustan en SQLite:

  1. Nos obliga a generar un paquete de nuestra aplicación por cada plataforma (x86, x64, ARM) aunque una vez en la tienda de Windows es transparente para el usuario.
  2. El fallo de los caracteres especiales en la ruta del path de la base de datos es realmente peligroso.
  3. Permite ejecutar sentencias SQL, para algunos esto será bueno, pero para mi es una aberración que no debería permitirse.

Pero a cambio, tiene algunas cosas muy buenas también:

  1. Me gusta como han implementado el soporte para TPL, el uso de async y await hace muy cómodo trabajar con la base de datos sin bloquear la aplicación.
  2. La multiplataforma: Linux, Mac OS X, Windows, iOS, Android, Symbian… es muy posible que si estás portando una app de otra plataforma, uses SQLite, al mismo tiempo, si piensas portar tu app Windows 8 a otra plataforma, podrás usar SQLite.

La última duda que tengo, es si será compatible con Windows Phone 8, supongo que para saber algo todavía tendremos que esperar un poco más!

Como siempre, aquí os dejo el código fuente de ejemplo que he usado en el artículo para que podáis trastear con el un poco. Espero que os haya resultado interesante!

Un saludo y Happy Coding!

3 comentarios sobre “[Windows 8] Bases de datos locales en aplicaciones metro (I)”

  1. Hola, respecto al problema de caracteres en el path de la base de datos, parece que puede ser un problema de encoding. Una vez que has descargado el paquete vía NuGet, en el fichero SQLite.cs añadido basta con reemplazar las llamadas a SQLite3.Open por SQLite3.Open16 en los constructores de la clase SQLiteConnection. Al parecer la diferencia es que la primera espera una ruta en UTF-8 y la segunda en UTF-16. Cambiando dichas llamadas se soluciona el problema.

  2. Hola Yeray, una duda? Es posible hacer multiples insercciones en una sola transacción? O cada insercción se ejecuta en una transacción.

    Un saludo.

Responder a smorales Cancelar respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *