Comprendre MongoDB depuis C#

Dans la lignée des articles du type “je ne comprends rien, mais je partage quand même” (voir les articles récents sur Async), voici quelques notes sur mon premier contact avec MongoDB. C’est une sorte de test, pour voir si la courbe d’apprentissage peut être rendue plus facile en montrant toutes les étapes de la prise de contact (y compris et surtout les erreurs et incompréhensions) plutôt qu’en expliquant directement le résultat correct.

Du coup, même avertissement que précédemment : ce que vous voyez ci-dessous n’est peut-être pas la bonne façon de faire. Ne vous en inspirez pas pour écrire du vrai code…

Installation

Histoire de faciliter la vie aux très grands débutants en MongoDB, comme moi, je vais détailler fortement les étapes de l’installation d’une configuration fonctionnelle en C#, sous Windows.

Première chose à faire : télécharger MongoDB pour Windows. Vous récupérez alors un fichier .zip, que vous décompressez où vous voulez. L’arborescence suivante est contenue :

image

Dans le premier répertoire, les licences et le README. Dans le répertoire bin, les programmes :

image

Lancement de MongoDB

Dans un premier temps, nous allons utiliser le plus simple moyen de démarrer MongoDB, en lançant monogd.exe. Apparemment, il est possible de lancer MongoDB en tant qu’un service Windows, mais pour nos tests, pas besoin.

Si on lance simplement mongod.exe, on obtient le message suivant :

image

Par défaut, MongoDB va chercher le répertoire \data\db comme repository de ses données, mais plantera si le répertoire n’existe pas. Par contre, on peut le faire pointer à un autre endroit, en utilisant la grammaire suivante :

mongod -dbpath=D:\Projets\MongoDB\data

Là, la console renvoie ce type de message :

Sun Oct 07 18:43:11 [initandlisten] MongoDB starting : pid=20448 port=27017 dbpath=D:\Projets\MongoDB\data 64-bit host=ANTARES
Sun Oct 07 18:43:11 [initandlisten] db version v2.2.0, pdfile version 4.5
Sun Oct 07 18:43:11 [initandlisten] git version: f5e83eae9cfbec7fb7a071321928f00d1b0c5207
Sun Oct 07 18:43:11 [initandlisten] build info: windows sys.getwindowsversion(major=6, minor=1, build=7601, platform=2, service_pack=’Service Pack 1′) BOOST_LIB
_VERSION=1_49
Sun Oct 07 18:43:11 [initandlisten] options: { dbpath: “D:\Projets\MongoDB\data” }
Sun Oct 07 18:43:11 [initandlisten] journal dir=D:/Projets/MongoDB/data/journal
Sun Oct 07 18:43:11 [initandlisten] recover : no journal files present, no recovery needed
Sun Oct 07 18:43:11 [initandlisten] waiting for connections on port 27017
Sun Oct 07 18:43:11 [websvr] admin web console waiting for connections on port 28017

Les deux dernières lignes nous indiquent le port pour les appels à MongoDB (27017 par défaut) et pour l’administration par la console web (28017 par défaut). Et si on jette un oeil dans le fameux répertoire pointé par l’option, voici ce qu’on y trouve :

image

Conséquentes, les tailles de fichiers, pour une base dans laquelle on n’a encore rien fait ! Mais il s’agit certainement de réservation de taille, comme pour des fichiers MDF…

Driver C# pour MongoDB

La base de données proprement dite étant lancée, on peut passer aux appels en C#. Par contre, il faut pour cela installer le driver, qu’on trouvera ici. Que vous utilisiez le .msi ou le .zip, vous obtiendrez deux librairies .NET qu’il faudra ajouter en référence au projet de test Visual Studio .NET que nous allons mettre en place, à savoir MongoDB.Driver.dll et MongoDB.Bson.dll (dans C:\Program Files (x86)\MongoDB\CSharpDriver 1.6 ou équivalent en fonction de votre système).

image

Le répertoire contient également le fichier .chm avec l’aide, même si le site de MongoDB, et particulièrement la section C# qui nous intéresse, sont très complets et bien réalisés.

Première connexion

Après avoir créé un projet de type console dans Visual Studio et ajouté les deux fichiers susnommés en référence, nous pouvons écrire le code suivant :

image

Il existe d’autres surcharges de MongoServer.Create, qui prennent en paramètre les types suivants :

  • MongoConnectionStringBuilder
  • MongoServerSettings
  • MongoUrl
  • Uri

Comme pour le lancement de la base de données, nous resterons sur le plus simple ici, à savoir la surcharge prenant une chaîne où on écrit la chaîne de connexion la plus simple possible, à savoir mongodb://[serveur]:[port].

Pour travailler dans MongoDB, il faut utiliser une database du server. Le plus simple dans un premier temps est d’utiliser la fonction qui permet de lister les bases de données, en espérant qu’on en trouvera une par défaut :

image

Ou bien, en ajoutant un using System.Linq :

image

C’est bien le cas :

image

Toujours dans le but de garder les choses simples, nous utiliserons cette base de données existant par défaut pour la suite des manipulations de test. Pour récupérer une instance .NET correspondant à cette database, nous utilisons le code suivant :

image

Notion de collection

Dans MongoDB, on parle de collection, car la notion de table est plus proche d’une structuration tabulaire forte, alors que dans MongoDB, on peut entrer des données structurées de manière plus lâche. Il semble qu’on puisse avoir des schémas des documents qu’on entre dans la base, mais qu’il soit possible de dériver et d’entrer des données différentes. Ne me demandez pas pourquoi : je suis en train d’apprendre, comme vous si vous lisez ceci Sourire.

Le contenu de ces collections est constitué de “documents”, stockés sous une forme BSON (Binary JSON). Quand on utilise C#, on peut associer une classe en correspondance avec le contenu d’une collection, ce qui permet d’utiliser plus facilement les données, en lecture comme en écriture. Pour ce premier test, nous allons donc commencer par créer une classe Personne, toute bête :

image

La surcharge de ToString() n’est pas importante, elle nous servira juste pour afficher les résultats des requêtes, si on arrive jusque là pour ce premier article. Pour utiliser une collection basée sur cette classe, nous allons mettre en place le code suivant :

image

Normalement, la fonction Insert devrait nous permettre de rentrer des données dans cette collection annuaire :

image

Ce code compile, mais il faut qu’on fasse quelque chose de plus pour voir si les données sont bien persistantes, à savoir arrêter la base de données, la relancer, et faire ce coup-ci un programme sans les Insert, mais avec une instruction permettant de compter les données :

image

Les ennuis commencent…

Le problème est que cette ligne de code renvoie 1 :

image

Oui, une entrée ! Je comprendrais zéro entrées, ce qui me ferait supposer que j’ai oublié quelque chose comme une instruction de type Commit. Je comprendrais aussi quatre, parce que j’ai rentré quatre données. Mais 1 ! Qu’est-ce qui peut bien faire qu’on a un résultat aussi bizarre ?

Voilà le problème dans le genre d’approche “on fonce dans le tas” que je vous propose de suivre ici : je ne connais rien à la façon dont fonctionne MongoDB, donc quelque chose qu’un habitué va voir comme évident me désoriente complètement…

En cherchant un peu dans la classe collection, je me suis rendu compte ensuite qu’il y avait une fonction Save, qu’on pouvait utiliser exactement de la même manière : c’est étonnant, car en général, une API ne donne qu’une seule façon de réaliser une fonction. Or, on peut écrire le code ci-dessous, et il compile :

image

Histoire de bien repartir de zéro à chaque fois (on enlèvera évidemment cette ligne plus tard), je fais ceci avant chaque nouvel essai :

image

Par contre, à la runtime, ça ne se passe pas bien, contrairement au même programme utilisant la fonction Insert :

image

Le message est un peu plus explicite, et il y a un semblant de logique sur l’obtention d’une seule ligne dans la persistance : comme il n’y avait pas d’Id, peut-être qu’il était créé par défaut, et du coup, les entrées suivantes n’étaient pas prises en compte ?

Un début de correction

Pour tester cette hypothèse, on change un peu la classe Personne :

image

Apparemment, MongoDB doit être basé sur des conventions, car le seul fait de mettre une propriété qui s’appelle Id a suffi pour changer le message d’erreur :

image

Donc, quelque chose a bien reconnu la présence de cette propriété Id, mais il manque un générateur d’Id pour que ça marche. Peut-être que si on met les Id à la main dans les lignes d’insertion, ça ira mieux ?

image

Effectivement, ça va beaucoup mieux, et on a bien désormais 4, comme résultat de la requête sur le nombre de documents dans la collection :

image

Ceci fonctionne d’ailleurs même si on arrête et relance la base de données MongoDB. Ainsi que si on enlève le DropDatabase et les instructions Save, donc la persistance fonctionne également. Cool, on commence à arriver à quelque chose Sourire.

Revenons à l’Insert

Si maintenant on essaie de revenir sur notre premier essai, est-ce que ça marche ?

image

Les résultats sont identiques, et on a bien quatre entrées dans la collection si on requête sur la persistance. Il doit pourtant bien y avoir une différence… Du coup, un peu de recherche sur internet, et on trouve la solution, qui est en relation avec la notion d’identifiant : Save met à jour au fur et à mesure qu’il rencontre le même identifiant, alors qu’Insert ne fera rien dans le même cas.

Du coup, supposons qu’on mette tous les identifiants à la même valeur comme ceci :

image

On a le même résultat qu’au début, à savoir une seule entrée. Pour savoir de laquelle il s’agit, nous ajoutons la ligne suivante, juste après le décompte :

image

Résultat :

image

Si maintenant, on fait la même chose avec un Save à la place d’Insert, voici ce qu’on obtient :

image

Voici la différence : l’Insert a créé dans la base une première entrée avec l’identifiant “a”, puis n’a rien fait sur les autres instances avec le même identifiant, car il était impossible d’ajouter l’entrée (mais étonnamment, MongoDB ne renvoie pas d’exception sur ce cas). Par contre, Save a à chaque fois écrasé la valeur précédente de l’entrée identifiée par “a”, et donc c’est la dernière ligne qui l’a emporté…

Faisons bien les choses

Du coup, le plus logique dans une utilisation normale est de mettre en place l’insertion de données avec la fonction Insert qui va bien. Mais en regardant à nouveau les fonctionnalités proposées, il se trouve qu’il y a un InsertBatch(). Or, la documentation explique qu’il faut utiliser cette fonction pour les insertions en volume, de façon à garder de bonnes performances. On réécrit donc le code comme ceci :

image

Maintenant, il ne nous reste plus qu’à tester une requête un peu plus sympa :

image

Coup de bol : ça marche du premier coup, et on récupère bien toutes les personnes dans la collection dont la propriété Nom a pour valeur Dupond :

image

Conclusion

Ca fait déjà pas mal de choses pour un premier test de MongoDB depuis C#, donc je vais arrêter là… Je vous redonne juste le code complet, qui est finalement très simple :

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

namespace TestConsole
{
    class Personne
    {
        public string Id { get; set; }
        public string Nom { get; set; }
        public string Prenom { get; set; }

        public override string ToString()
        {
            return string.Format("{0} {1}", Prenom, Nom);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            MongoServer server = MongoServer.Create("mongodb://localhost:27017");
            //server.GetDatabaseNames().ToList().ForEach(n => Console.WriteLine(n));
            //server.DropDatabase("local");
            MongoDatabase db = server.GetDatabase("local"); 
            MongoCollection<Personne> annuaire = db.GetCollection<Personne>("annuaire");

            annuaire.InsertBatch(new List<Personne>()
            {
                new Personne() { Id = "a", Nom = "Dupond", Prenom = "Jean" },
                new Personne() { Id = "b", Nom = "Lefebvre", Prenom = "René" },
                new Personne() { Id = "c", Nom = "Dupond", Prenom = "Mathieu" },
                new Personne() { Id = "d", Nom = "Dupond", Prenom = "Sergio" }
            });

            Console.WriteLine(annuaire.FindAll().Count());

            var query = new QueryDocument("Nom", "Dupond");
            foreach (Personne r in annuaire.FindAs<Personne>(query))
                Console.WriteLine(r);
        }
    }
}

A nouveau, ça doit paraitre absolument simpliste à quelqu’un qui connait MongoDB, mais j’espère que cette approche pas-à-pas, erreur après erreur, aidera d’autres débutants à comprendre plus vite.

About JP Gouigoux

Jean-Philippe Gouigoux est Architecte Logiciel, MVP Connected Systems Developer. Il intervient régulièrement à l'Université de Bretagne Sud ainsi qu'à l'Agile Tour. Plus de détails sur la page "Curriculum Vitae" de ce blog.
This entry was posted in Uncategorized and tagged . Bookmark the permalink.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Captcha Captcha Reload