Alexandru V. Simonescu

Software Craftsmanship and Android Development

Introducción a MongoDB y NoSQL en C# (Spanish)

Article written in spanish, as exported from my old spanish blog. All new published articles are in english, as is the main language of the blog.

Este artículo no pretende ser ninguna guía definitiva para trabajar con MongoDB en C# pero si se quiere dar una visión introductoria para que cualquiera que no haya tocado nunca este tema, sepa por donde comenzar.

  • Conceptos básicos de NoSQL
  • Instalación de MongoDB en Windows
  • La consola de MongoDB
  • Operaciones CRUD en MongoDB
  • Identificador único MongoDB
  • Conexión desde C#
  • Manejo de datos desde C#
  • Recuperar colecciones desde C#
  • Enlaces de interés

Conceptos básicos de NoSQL

Al contrario que en las bases de datos relacionales, donde se respetan unas características ACID, mantienen una estructura bien definida y proporcionan consistencia. Las NoSQL tienen en cuenta otros factores cómo la flexibilidad, la velocidad y la capacidad de escalado horizontal. Estas últimas en lugar de almacenar relaciones de datos, guardan documentos con estructuras arbitrarias. Características más destacables de las bases de datos NoSQL:

  • Enorme flexibilidad que brinda en los esquemas de datos.
  • Facilidad de uso.
  • Alto rendimiento y escalabilidad.
  • Facilidad de mantenimiento.
  • Capacidad de funcionar sin puntos de fallo, pueden recuperarse aunque se caiga cualquiera de sus nodos.
  • La forma de extraer datos no tiene nada que ver con las bases de datos relacionales.

Como aclaración, una base de datos documental no es lo mismo que el concepto de NoSQL, en realidad las documentales son una categoría dentro de las NoSQL que incluyen almacenes del tipo clave-valor, almacenes N-tuplas y algunas cosas más.

Instalación de MongoDB en Windows

Lo primero es ir a la página oficial de MongoDB y descargar el motor de bases de datos, en mi caso he descargado el .zip para las versiones de Windows de 64 bits. Lo siguiente es descomprimirla, yo he optado por hacerlo en C:\MondoDB pero vosotros sois libres de hacerlo donde queráis. El siguiente paso es crear una carpeta llamada “data”, que es donde Mongo va a guardar las bases de datos, por defecto se guardan en C:\data\db.

Para indicarle a Mongo que su ruta de inicio para guardar las bases de datos es otra carpeta a la establecida por defecto, tenemos que pasarle un parámetro al iniciarlo. Para eso podemos crear un fichero .bat con este contenido: mongod.exe –dbpath c:\MongoDB\data y lo guardamos en la carpeta “bin”, ahora al iniciar el servidor, en vez de usar mongod.exe usaremos nuestro fichero, que en mi caso lo he llamado mongod.bat. Una vez arrancado el servidor si vamos a la carpeta “data”, veremos que ya se han creado unos ficheros. Otra forma de arrancar MongoDB es como servicio, de forma transparente al usuario. Para esta introducción nos limitaremos a arrancarlo nosotros mismos de forma manual, si os interesa instalarlo como servicio una búsqueda rápida en la página oficial de MongoDB os solucionará las dudas. Iniciemos el servidor dándole a mongod.bat y luego para conectarnos usaremos mongo.exe.

En la ventana del cliente podemos insertar un objeto de prueba de esta forma:

db.alexsimo.save({nombre: "alex"})
db.alexsimo.find()

La primera línea guardaría un objeto en la base de datos alexsimo mientras que la segunda recuperaría todos los objetos contenidos en dicha base de datos. Otra forma de conectarnos a Mongo, a parte del cliente de consola, es usar la interfaz web de administración, por defecto Mongo escucha en el puerto 28018, conectándonos a ese puerto a través de un navegador nos saldría la interfaz web.

 La consola de MongoDB

La consola de Mongo está basada en el motor V8 de Chrome, al igual que Node.JS y otras muchas aplicaciones, podemos hacer cualquier tipo de operación al igual que la haríamos en JavaScript (asignaciones, sumas, restas, etc.).

var n1 = 10;
var n2 = 15;
n1 + n2;
//output: 25

También podemos llamar a las funciones nativas de JavaScript, crear funciones, objetos y usarlos dentro de la misma consola. Para ver todos los comandos disponibles escribimos help. Para obtener la ayuda de como interactuar con la base de datos usamos db.help(). Para ver todas las bases de datos que tenemos creadas usamos show dbs. Por defecto viene con una base de datos llamada local. Para usar una de las bases de datos existentes se lo indicaremos a Mongo mediante el comando use seguido del nombre de la base de datos (use alexsimodb). Dentro de una base de datos NoSQL, los datos se almacenan en colecciones, que es lo más parecido a las tablas de las bases de datos relaciones. Con show collections obtendríamos por pantalla todas las colecciones existentes. La consola cuenta con una serie de objetos globales por defecto, para ver cuales hay que ir al listado de APIs para la versión actual de Mongo. En nuestra carpeta del usuario tendremos un fichero llamado .mongorc.js, cualquier función o sentencia JavaScript definida dentro de este fichero estará disponible de forma global dentro del contexto de Mongo.  

Operaciones CRUD básicas

Como ya hemos mencionado, MongoDB tiene bases de datos, y dentro de las bases de datos tenemos colecciones, que vienen siendo como las tablas. A parte de usar la consola para conectarnos al servidor, en el mercado hay otras opciones como MongoVUE para escritorio o RockMongo para entornos web (hecho en PHP).

Inserción

Para ver como se guardan elementos, antes crearemos un pequeño objeto JSON en la consola de Mongo:

var proyecto1 = {
   nombre: "Portal Colaborativo",
   estimacion: "3 meses",
   miembros: 5
}

Ahora para guardar ese objeto haremos lo siguiente:

db.proyectos.insert(proyecto1);

Voy a explicar esta línea, db se refiere a la base de datos actual (en mi caso test), proyectos es la colección donde guardaremos el objeto, insert guarda el objeto pasado como parámetro en la colección indicada anteriormente. En mi caso, proyectos no existe, pero se creará antes de insertar el objeto proyecto1. Si no sale ningún error la inserción se habrá realizado correctamente.

También podemos listar los proyectos en la consola con db.proyectos.find(). Vamos a insertar otro objeto en la base de datos pero sin haber creado previamente el objeto a insertar:

db.proyectos.insert({
   nombre: "CMS",
   estimacion: "2 meses",
   miembros: 2,
   presupuesto: 12000
});

En esta última inserción hemos introducido un campo nuevo, presupuesto. Al hacer db.proyectos.find() veremos que se ha insertado. Si queremos buscar todos los proyectos que incluyan la palabra “CMS” en su nombre haríamos esto:

db.proyectos.find({nombre:"CMS"});

Actualización

Para actualizar podemos hacer esto:

proyecto1.presupuesto = 10000;
db.proyectos.update({nombre:"CMS"}, proyecto1);

Esto actualizaría los datos del proyecto que contenga la palabra “CMS” en su nombre con los valores del objeto proyecto1 al que se le ha añadido un nuevo campo presupuesto con el valor 10000.

Borrado

Si queremos borrar haremos lo siguiente:

db.proyectos.remove({nombre:"CMS"});

Borraría los proyectos que cumplan ese patrón, que el nombre contenga la palabra “CMS”.

Lectura

Ya hemos visto que para leer podemos usar db.proyectos.find() y nos devolvería todos los registros, a find() le podemos pasar un objeto JSON para filtrar resultados.  

Identificador único

Todos los registros en MongoDB tienen un identificador único, que si dejamos que se genere por defecto será el campo _id o también llamado ObjectId. Este ID al contrario que en otros sistemas relaciones donde se autoincrementa, en MongoDB se usan otras técnicas para generarlo: 4 bytes representan el tiempo transcurrido desde la fecha epoch de Unix, los tres siguientes bytes se generan a partir del nombre de red de la máquina y aplicándole una función hash, los dos siguientes son el identificador del proceso que genera el ObjectId, los tres últimos bytes son un contador incremental.  

Conexión a MongoDB  desde C

Antes de nada tenemos que descargar el driver de MongoDB para C#. Para eso vamos al repositorio oficial y descargamos el .msi que nos guiará a través de la instalación. También podemos usar NuGet para instalar la .dll dentro de nuestro proyecto. Yo voy a instalarlo desde NuGet, cosa que os recomiendo a vosotros también.

A continuación voy a crear un proyecto de consola donde haremos las pruebas sobre MongoDB. Agregamos el driver para nuestro proyecto y podremos proceder a programar. El código de abajo se explicará por si mismo, aún así explicaré brevemente su funcionamiento:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MongoDB.Bson;
using MongoDB.Driver;

namespace MongoDB
{
    class Program
    {
        static void Main(string[] args)
        {
            // parámetros de conexión
            MongoUrl mu = new MongoUrl("mongodb://localhost");

            // cliente mongodb
            MongoClient mc = new MongoClient(mu);

            // objeto referencia al servidor
            MongoServer mongo = mc.GetServer();

            // obtener nombre de todas las bases de datos
            var bases = mongo.GetDatabaseNames();

            foreach (string db in bases)
            {
                Console.WriteLine("Base de datos: {0}", db);
            }

        }
    }
}

La variable mu contiene los parámetros de conexión a la base de datos, en mi caso solo contiene la URL pero se le pueden añadir muchos más, como el timeout time. La variable mc es el cliente que apunta al servidor configurado en mu. Cómo último mongo establece la conexión al servidor y es con este objeto con el que trabajaremos de ahí en adelante. En bases se guardan los nombres de todas las bases contenidas en el servidor y mediante un foreach se listan por pantalla. El código de arriba sólo muestra las bases de datos existentes, pero si queremos conectarnos una base específica haremos lo siguiente:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MongoDB.Bson;
using MongoDB.Driver;

namespace MongoDB
{
    class Program
    {
        static void Main(string[] args)
        {
            // parámetros de conexión
            MongoUrl mu = new MongoUrl("mongodb://localhost");

            // cliente mongodb
            MongoClient mc = new MongoClient(mu);

            // objeto referencia al servidor
            MongoServer mongo = mc.GetServer();

            // seleccionar bd
            MongoDatabase mdb = mongo.GetDatabase("test");

            // obtener colecciones de la db
            var cols = mdb.GetCollectionNames();

            foreach (string col in cols)
            {
                Console.WriteLine("Coleccion: {0}", col);
            }

        }
    }
}

Lo único que hemos añadido es la variable mdb del tipo MongoDatabase donde obtenemos una referencia a la base de datos “test”. En el foreach listamos las colecciones una vez obtenidas y guardadas en la variable cols.  

Manejo de datos

Para este ejemplo añadiremos una clase a nuestro proyecto de Visual Studio, llamada LogEvento. En este ejemplo haremos una clase que guarde unos eventos en la base de datos, en forma de logs.

using System;
using MongoDB.Bson;

namespace MongoDB
{
    class LogEvento
    {
        public ObjectId _id { get; set; }
        public string Usuario { get; set; }
        public DateTime Fecha { get; set; }
        public string Nombre { get; set; }
        public string Evento { get; set; }
        public string Comentario { get; set; }  

        public LogEvento() { }
        public LogEvento(string us, DateTime fe, string no, string ev, string com)
        {
            Usuario = us;
            Fecha = fe;
            Nombre = no;
            Evento = ev;
            Comentario = com;
        }

    }
}

La clase de arriba se usará para guardar objetos de ese tipo en la BD, modificando un poco un código anterior insertaremos un objeto del tipo LogEvento en la BD.

using System;
using MongoDB.Driver;

namespace MongoDB
{
    class Program
    {
        static void Main(string[] args)
        {
            // parámetros de conexión
            MongoUrl mu = new MongoUrl("mongodb://localhost");

            // cliente mongodb
            MongoClient mc = new MongoClient(mu);

            // objeto referencia al servidor
            MongoServer mongo = mc.GetServer();

            // seleccionar bd
            MongoDatabase mdb = mongo.GetDatabase("test");

            // seleccionar coleccion donde guardar eventos
            MongoCollection eventosCol = mdb.GetCollection<LogEvento>("eventos");

            // crear evento para guardar en la bd
            LogEvento evento = new LogEvento
                                   {
                                       Usuario = "AlexSimo",
                                       Evento = "Fichero borrado",
                                       Nombre = "Informe_finaciero.pdf",
                                       Comentario = "Borrar",
                                       Fecha = DateTime.Now
                                   };

            eventosCol.Insert<LogEvento>(evento);

        }
    }
}

Por la propiedad _id no nos preocupemos, ya que lo genera el driver de Mongo. Si ejecutamos la sentencia para obtener todos los registros de Mongo veremos que nuestro nuevo registro se ha insertado. Para modificar un registro que ya está en la base de datos, un update de toda la vida, se haría de la siguiente forma:

using System;
using MongoDB.Driver;

namespace MongoDB
{
    class Program
    {
        static void Main(string[] args)
        {
            // parámetros de conexión
            MongoUrl mu = new MongoUrl("mongodb://localhost");

            // cliente mongodb
            MongoClient mc = new MongoClient(mu);

            // objeto referencia al servidor
            MongoServer mongo = mc.GetServer();

            // seleccionar bd
            MongoDatabase mdb = mongo.GetDatabase("test");

            // seleccionar coleccion donde guardar eventos
            MongoCollection eventosCol = mdb.GetCollection<LogEvento>("eventos");

            // crear evento para guardar en la bd
            LogEvento evento = new LogEvento
                                   {
                                       Usuario = "Administrador",
                                       Evento = "Fichero subido",
                                       Nombre = "LibretaContactos.pdf",
                                       Comentario = "Subir",
                                       Fecha = DateTime.Now
                                   };

            // inserta objeto
            eventosCol.Insert<LogEvento>(evento);

            // modificamos objeto
            evento.Usuario = "Analista";

            // persistimos objeto
            eventosCol.Save(evento);

        }
    }
}

Recuperar colecciones

En este apartado veremos como recuperar una colección y trabajar con los datos almacenados dentro de esa colección.

using System;
using MongoDB.Driver;
using MongoDB.Driver.Builders;

namespace MongoDB
{
    class Program
    {
        static void Main(string[] args)
        {
            // parámetros de conexión
            MongoUrl mu = new MongoUrl("mongodb://localhost");

            // cliente mongodb
            MongoClient mc = new MongoClient(mu);

            // objeto referencia al servidor
            MongoServer mongo = mc.GetServer();

            // seleccionar bd
            MongoDatabase mdb = mongo.GetDatabase("test");

            // seleccionar coleccion donde guardar eventos
            MongoCollection eventosCol = mdb.GetCollection<LogEvento>("eventos");

            // filtro de busqueda
            var query = Query<LogEvento>.EQ( evento => evento.Usuario, "Administrador");

            // buscar pasando el filtro
            LogEvento eventoFiltrado = eventosCol.FindOneAs<LogEvento>(query);

            // mostrar evento
            Console.WriteLine("Evento filtrado por Administrador - Fecha:{0} - Nombre: {1}",
                eventoFiltrado.Fecha, eventoFiltrado.Nombre);

        }
    }
}

Este código es sencillo y con los comentarios se explica por si mismo, pero aclararé algunas cosas. La variable query contiene la consulta que mas tarde filtrará los datos a recuperar, el método EQ de Query usa una función lambda para filtrar datos. Luego se guarda el evento seleccionado en la variable eventoFiltrado, con el método FindOneAs() sólo se nos devolverá un registro. Una forma mas adecuada y que nos permite usar todas las ventajas y funcionalidades que nos proporciona C# es usar Linq para seleccionar los datos que nos interesen.

using System;
using System.Linq;
using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.Builders;
using MongoDB.Driver.Linq;

namespace MongoDB
{
    class Program
    {
        static void Main(string[] args)
        {
            // parámetros de conexión
            MongoUrl mu = new MongoUrl("mongodb://localhost");

            // cliente mongodb
            MongoClient mc = new MongoClient(mu);

            // objeto referencia al servidor
            MongoServer mongo = mc.GetServer();

            // seleccionar bd
            MongoDatabase mdb = mongo.GetDatabase("test");

            // seleccionar coleccion donde guardar eventos
            MongoCollection eventosCol = mdb.GetCollection<LogEvento>("eventos");

            // adaptar la colección al uso de Linq
            var logs = eventosCol.AsQueryable<LogEvento>();

            // seleccionar con Linq
            var res = from l in logs
                      where l.Usuario == "AlexSimo"
                      select l;

            // mostrar evento
            foreach (LogEvento log in res)
            {
                Console.WriteLine("Evento filtrado por AlexSimo: {0}",log.ToJson());
            }

        }
    }
}

En este ejemplo en vez de usar filtros de tipo Query contenidos en el namespace del driver de MongoDB he optado por usar consultas Linq para filtrar los datos.  

Enlaces de interés

Documentación de la API de C# de MongoDB.