Introducción
Tras la llegada de Realm a Xamarin con la promesa de una opción de base de datos sencilla y sobretodo con mejor rendimiento que el resto de opciones (incluida SQLite, la opción más usada y extendida), la duda es obvia…¿cuál usar?. En este artÃculo vamos a realizar unas pruebas básicas con las operaciones de CRUD para medir en tiempos el rendimiento exacto de cada opción. Además verificaremos otros aspectos como la facilidad de uso, el nivel de documentación y otros aspectos.
¿Os apuntáis?
Realm
Realm es una base de datos gratuita pensada para aplicaciones móviles, tabletas o wearables siendo una alternativa interesante a SQLite. Llega con el gran objetivo en mente de conseguir un alto rendimiento manteniendo una alta facilidad de uso.
SQLite
SQLite es un motor de base de datos Open Source utilizado en todas las plataformas móviles y adoptado tanto por Apple como Google como Microsoft. El uso de SQLite en aplicaciones móviles es una gran opción ya que:
- La base de datos es pequeña y fácil de portar.
- La base de datos se concentra en un pequeño archivo.
- Implementa la mayor parte del estándar SQL92.
La aplicación para realizar pruebas
En nuestra aplicación necesitamos una única vista donde contaremos con botones para realizar las acciones básicas del CRUD junto a un área donde mostrar los resultados.
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" Â Â Â Â Â Â Â Â Â Â Â Â xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" Â Â Â Â Â Â Â Â Â Â Â Â x:Class="SqliteVsRealm.Views.MainView" Â Â Â Â Â Â Â Â Â Â Â Â Title="SQLite VS Realm"> Â <ContentPage.Content> Â Â Â <Grid> Â Â Â Â Â <Grid.RowDefinitions> Â Â Â Â Â Â Â <RowDefinition Height="20"/> Â Â Â Â Â Â Â <RowDefinition Height="50"/> Â Â Â Â Â Â Â <RowDefinition Height="50"/> Â Â Â Â Â Â Â <RowDefinition Height="50"/> Â Â Â Â Â Â Â <RowDefinition Height="20"/> Â Â Â Â Â Â Â <RowDefinition Height="*"/> Â Â Â Â Â </Grid.RowDefinitions> Â Â Â Â Â <Button Grid.Row="1" Â Â Â Â Â Â Â Â Â Â Â Â Â Text="Insert 1000 Items" Â Â Â Â Â Â Â Â Â Â Â Â Â Command="{Binding InsertCommand}"/> Â Â Â Â Â <Button Grid.Row="2" Â Â Â Â Â Â Â Â Â Â Â Â Â Text="Query 1000 Items" Â Â Â Â Â Â Â Â Â Â Â Â Â Command="{Binding QueryCommand}"/> Â Â Â Â Â <Button Grid.Row="3" Â Â Â Â Â Â Â Â Â Â Â Â Â Text="Delete 1000 Items" Â Â Â Â Â Â Â Â Â Â Â Â Â Command="{Binding DeleteCommand}"/> Â Â Â Â Â <Editor Grid.Row="5" Â Â Â Â Â Â Â Â Â Â Â Â Â Text="{Binding Log}" Â Â Â Â Â Â Â Â Â Â Â Â Â FontSize="10"/> Â Â Â </Grid> Â </ContentPage.Content> </ContentPage>
El resultado visual:
Enlazamos la View con la ViewModel estableciendo una instancia de la ViewModel a la propiedad BindingContext de la página.
BindingContext = App.Locator.MainViewModel;
En cuanto a cada botón, cada uno de ellos estará enlazado a un comando:
private ICommand _insertCommand; private ICommand _queryCommand; private ICommand _deleteCommand; Â public ICommand InsertCommand { Â Â Â Â get { return _insertCommand = _insertCommand ?? new DelegateCommand(InsertCommandExecute); } } Â public ICommand QueryCommand { Â Â Â Â get { return _queryCommand = _queryCommand ?? new DelegateCommand(QueryCommandExecute); } } Â public ICommand DeleteCommand { Â Â Â Â get { return _deleteCommand = _deleteCommand ?? new DelegateCommand(DeleteCommandExecute); } } Â private async void InsertCommandExecute() { Â } Â private async void QueryCommandExecute() { Â } Â private async void DeleteCommandExecute() { Â }
Sencillo, ¿cierto?.
La comparativa
A continuación, vamos a utilizar la aplicación creada para medir rendimiento de ambas opciones además de valorar otros aspectos de peso a la hora de determinar el uso de una u otra opción. Vamos a valorar aspectos como:
- Facilidad de uso
- Rendimiento
- Documentación
- Mantenimiento
Facilidad de uso
En el arranque de uso de SQLite y Realm tenemos que utilizar paquetes NuGet. En el caso de Realm todo viene empaquetado en un único paquete mientras que en el caso de SQLite utilizaremos varios paquetes (SQLite.Net-PCL, SQLite.Net.Core-PCL y SQLite.Net.Async-PCL).
Tras añadir los paquetes debemos realizar la configuración básica. En el caso de Realm es realmente reducida mientras que en SQLite si que necesitamos un poco de trabajo para especificar la ruta a la base de datos en cada plataforma para poder establecer la conexión.
public interface ISQLite { Â Â Â Â SQLiteAsyncConnection GetConnection(); }
La implementación de ISQLite nos permite establecer la conexión con la base de datos.
[assembly: Dependency(typeof(SQLiteClient))] namespace TodoSqlite.Droid.Services {    public class SQLiteClient : ISQLite    {        public SQLiteAsyncConnection GetConnection()        {            var sqliteFilename = "Todo.db3";            var documentsPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);             var path = Path.Combine(documentsPath, sqliteFilename);             var platform = new SQLitePlatformAndroid();             var connectionWithLock = new SQLiteConnectionWithLock(                                         platform,                                         new SQLiteConnectionString(path, true));             var connection = new SQLiteAsyncConnection(() => connectionWithLock);             return connection;        }    } }
Por último, el trabajo con cada base de datos es realmente similar. Tenemos métodos para cada acción básica, CRUD y tareas de gestión y mantenimiento directamente en C#. Veamos como obtener datos de una tabla.
En Realm:
public IList<TodoItem> GetAll() { Â Â Â Â var result = _realm.All<TodoItem>().ToList(); Â Â Â Â Â return result; }
En SQLite:
public async Task<IList<TodoItem>> GetAll() { Â Â Â Â var items = new List<TodoItem>(); Â Â Â Â using (await Mutex.LockAsync().ConfigureAwait(false)) Â Â Â Â { Â Â Â Â Â Â Â Â Â items = await _sqlCon.Table<TodoItem>().ToListAsync().ConfigureAwait(false); Â Â Â Â } Â Â Â Â Â return items; }
El número de lÃneas y opciones en este caso estan realmente parejas. Sin embargo, la preparación inicial es practicamente nula en el caso de Realm.
Ganador: Realm
Rendimiento
Llegamos a uno de los puntos fuertes o generalmente de mayor preocupación. Desde Realm se habla del rendimiento como una de sus principales bazas, pero…¿es realmente más rápido?, ¿cuánto más?.
A continuación, vamos a centrarnos en la lógica de cada botón de nuestra interfaz. A la hora de insertar datos:
[sourcecode language=»vb»]
private async void InsertCommandExecute()
{
    var watch = System.Diagnostics.Stopwatch.StartNew();
    for (int i = 0; i < 1000; i++)
    {
         await _sqliteService.Insert(new SqliteTodoItem
         {
              Id = i + 1,
              Name = string.Format("Name {0}", i + 1),
              Notes = string.Format("Notes {0}", i + 1),
              Done = false
         });
    }
    watch.Stop();
Â
    Log += $"SQLite: INSERT 1000 items in {watch.ElapsedMilliseconds} milliseconds\n";
Â
    watch.Restart();
    for (int i = 0; i < 1000; i++)
    {
         _realmService.Insert(new RealmTodoItem
         {
              Id = i + 1,
              Name = string.Format("Name {0}", i + 1),
              Notes = string.Format("Notes {0}", i + 1),
              Done = false
         });
     }
     watch.Stop();
Â
     Log += $"Realm: INSERT 1000 items in {watch.ElapsedMilliseconds} milliseconds\n";
}
[/sourcecode]
Insertamos 1000 elementos en cada una de las base de datos con los siguientes resultados de media:
- SQLite: 2400 milisegundos
- Realm 1800 milisegundos
A la hora de obtener los 1000 registros almacenados:
[sourcecode language=»vb»]
private async void QueryCommandExecute()
{
    var watch = System.Diagnostics.Stopwatch.StartNew();
    var sqliteResult = await _sqliteService.GetAll();
    watch.Stop();
Â
    Log += $"SQLite: QUERY 1000 items in {watch.ElapsedMilliseconds} milliseconds\n";
Â
    watch.Restart();
    var realmResult = _realmService.GetAll();
    watch.Stop();
Â
    Log += $"Realm: QUERY 1000 items in {watch.ElapsedMilliseconds} milliseconds\n";
}
[/sourcecode]
Los tiempos de media son:
- SQLite: 50 milisegundos
- Realm: 2 milisegundos
Por último, llegamos al momenos de eliminar registros:
[sourcecode language=»vb»]
private async void DeleteCommandExecute()
{
    var sqliteResult = await _sqliteService.GetAll();
    var watch = System.Diagnostics.Stopwatch.StartNew();
    foreach (var item in sqliteResult)
    {
         await _sqliteService.Remove(item);
    }
    watch.Stop();
Â
    Log += $"SQLite: DELETE 1000 items in {watch.ElapsedMilliseconds} milliseconds\n";
Â
    var realmResult = _realmService.GetAll();
    watch.Restart();
    foreach (var item in realmResult)
    {
         _realmService.Remove(item);
    }
    watch.Stop();
Â
    Log += $"Realm: DELETE 1000 items in {watch.ElapsedMilliseconds} milliseconds\n";
}
[/sourcecode]
Los resultados son:
- SQLite: 2300 milisegundos
- Realm: 1300 milisegundos
Los tiempos en general en Realm son más reducidos que con SQLite, por lo que podemos decir que efectivamente el rendimiento en general es mejor. Optimizando (transacciones, etc) podemos mejorar ligeramente el rendimiento en SQLite dejando los resultados aún más cercanos. Realm hace mejor el trabajo sin nada especial adicional.
Ganador: Realm
Documentación
Realm ha creado una documentación especÃfica para desarrolladores Xamarin bastante completa. Sin embargo, si comparamos con SQLite y debido fundamentalmente a la maduración y uso de cada uno de ellos, SQLite cuenta con mayor documentación. Xamarin cuenta con documentación y ejemplos utilizando SQLite sumado a la comunidad a nivel de artÃculos y ejemplos, hace que hoy por hoy, el acceso y aprendizaje de cada opción este mejor documentada en SQLite.
Ganador: SQLite
Mantenimiento
Ya hemos revisado aspectos importante relacionados con el arranque en cada uno de ellos, el acceso inicial y facilidad de uso. Sin embargo, creo bastante positivo el análisis del mantenimiento en cada caso en proyectos de peso.
El primer punto de impacto radica en la forma de trabajo con modelos de Realm. Dado que deben heredar de RealmObject y la creación debe realizarse utilizando Realm.CreateObject impidiendo la creación mediante un constructor simple. Igualmente en listas se utiliza RealmList en lugar de POCOs como hace SQLite que sencillamente añade ciertas etiquetas para saber como trabajar correctamente en cada caso. De cara a una correcta gestión de dependencias y testing podemos llegar a tener problemas con Realm necesitando una duplicidad de modelos con sencillos POCOs. Es posible aunque afecta sin duda al punto de facilidad de uso.
El segundo punto importante que puede afectar es la falta de soporte a Async en Realm. Damos por hecho que se añadirá soporte en futuras versiones de Realm, sin embargo, en la versión actual la falta de métodos asÃncronos puede afectar en la gestión y mantenimiento de nuestro código.
Ganador: SQLite
Conclusiones
Llegados a este punto, cuatro evaluciones, dos con SQLite como vencedor, otras dos para Realm. ¿Qué usar?. La respuesta en este caso, como en la mayorÃa de casos, es depende. Realm se muestra como una interesante alternativa dirÃa que perfectamente válida en pequeños proyectos con una cantidad baja de modelos. Su facilidad de arranque, uso y rendimiento lo hacen idóneo. Sin embargo, ante proyectos de peso con un gran mantenimiento la orientación al menos por ahora, seguirÃa decantándose por SQLite. El apoyo de los principales actores en el ambito móvil y la comunidad, hace que sea la opción más madura, sólida y documentada disponible.
Más información
- Xamarin: Cross-Platform Data Access
- Xamarin: Working with a Local Database
- Realm: Introducing Realm Xamarin
- Blog de Xamarin: Cross-Platform Development with Xamarin.Forms and Realm