Intégration de MongoDB dans une API Web .NET Core 3.1

Ce guide technique explore la mise en œuvre de MongoDB au sein d'une application API Web basée sur .NET Core 3.1. Il inclut des exemples de commandes MongoDB, une interfaec d'accès aux données en C#, un service d'implémentation et un contrôleur API pour des opérations de base.

Commandes MongoDB pour les tests

// Création d'un utilisateur avec droits en lecture/écriture
db.createUser({
    user: 'utilisateur_appli',
    pwd: 'secretMotDePasse',
    roles: ["readWrite"]
})

// Recherche sans afficher le champ _id
db.collection_eleves.find({}, {_id: 0});

// Mise à jour d'un document identifié par id_eleve=2
db.collection_eleves.update({id_eleve: 2}, [{$set: {nom: 'nouveauNom'}}]);

// Sélection des documents possédant le champ 'age'
db.collection_eleves.find({age: {$exists: true}}, {_id: 0});

// Insertion avec la date courante
db.collection_eleves.insert({id_eleve: 3, nom: 'test', dateNaissance: new Date()});

// Ajout d'un champ 'telephone' à tous les documents
db.collection_eleves.updateMany({}, {$set: {telephone: 9876543210}})

// Ajout d'un élément dans un tableau 'livres'
db.collection_eleves.updateOne({id_eleve: 3}, {$push: {livres: 'NouveauLivre'}})

// Retrait d'un élément du tableau 'livres'
db.collection_eleves.updateOne({id_eleve: 3}, {$pull: {livres: 'AncienLivre'}})

// Lancement d'un conteneur Docker pour MongoDB
docker run -itd --name serveur_mongodb --restart=always -p 27017:27017 mongo

Interface d'accès aux données MongoDB en C#

using MongoDB.Bson;
using MongoDB.Driver;
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Threading.Tasks;

namespace MonProjet.Data
{
    public interface IRepositoryMongo<t> where T : class, new()
    {
        IMongoCollection<t> ObtenirCollection();

        IMongoCollection<t> ObtenirCollection(string nomBase, string chaineConnexion = "");

        bool InsererUn(T entite);

        Task<bool> InsererUnAsync(T entite);

        bool InsererPlusieurs(IEnumerable<T> entites);

        Task<bool> InsererPlusieursAsync(IEnumerable<T> entites);

        T TrouverParCritere(Expression<Func<T, bool>> critere);

        Task<T> TrouverParCritereAsync(Expression<Func<T, bool>> critere);

        IEnumerable<T> ListerParCritere(Expression<Func<T, bool>> critere);

        Task<IEnumerable<T>> ListerParCritereAsync(Expression<Func<T, bool>> critere);

        Task<DonneesPage> ListerAvecPaginationEtTriAsync(Expression<Func<T, bool>> filtre, bool triAscendant, Expression<Func<T, object>> colonneTri, int page = 1, int taillePage = 20);

        Task<DonneesPage> ListerAvecPaginationAsync(Expression<Func<T, bool>> filtre, int page, int taillePage);

        Task<List<T>> RechercherParDictionnaire(Dictionary<string, object> criteres);

        void Modifier(T entite, ObjectId id);

        bool Modifier(T entite);
    }
}
</t></t></t>

Service d'implémentation MongoDB

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.Linq;

namespace MonProjet.Services
{
    public class ServiceMongo<T> : IRepositoryMongo<T> where T : class, new()
    {
        private static readonly string _chaineConnexion = "mongodb://utilisateur_appli:secretMotDePasse@hote_exemple:27017/base_principale";
        private static readonly string _nomBase = "base_principale";

        public IMongoCollection<T> ObtenirCollection()
        {
            var client = new MongoClient(_chaineConnexion);
            var baseDonnees = client.GetDatabase(_nomBase);
            return baseDonnees.GetCollection<T>(typeof(T).Name);
        }

        public IMongoCollection<T> ObtenirCollection(string nomBase, string chaineConnexion = "")
        {
            MongoClient client;
            if (string.IsNullOrEmpty(chaineConnexion))
            {
                client = new MongoClient(_chaineConnexion);
            }
            else
            {
                client = new MongoClient(chaineConnexion);
            }
            return client.GetDatabase(nomBase).GetCollection<T>(typeof(T).Name);
        }

        public bool InsererUn(T entite)
        {
            try
            {
                ObtenirCollection().InsertOne(entite);
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        public async Task<bool> InsererUnAsync(T entite)
        {
            try
            {
                await ObtenirCollection().InsertOneAsync(entite);
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        public bool InsererPlusieurs(IEnumerable<T> entites)
        {
            try
            {
                ObtenirCollection().InsertMany(entites);
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        public async Task<bool> InsererPlusieursAsync(IEnumerable<T> entites)
        {
            try
            {
                await ObtenirCollection().InsertManyAsync(entites);
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        public T TrouverParCritere(Expression<Func<T, bool>> critere)
        {
            var filtre = new FilterDefinitionBuilder<T>().Where(critere);
            var resultat = ObtenirCollection().Find(filtre);
            return resultat.FirstOrDefault();
        }

        public async Task<T> TrouverParCritereAsync(Expression<Func<T, bool>> critere)
        {
            var filtre = new FilterDefinitionBuilder<T>().Where(critere);
            var resultat = await ObtenirCollection().FindAsync(filtre);
            return await resultat.FirstOrDefaultAsync();
        }

        public IEnumerable<T> ListerParCritere(Expression<Func<T, bool>> critere)
        {
            var filtre = new FilterDefinitionBuilder<T>().Where(critere);
            var resultat = ObtenirCollection().Find(filtre);
            return resultat.ToList();
        }

        public async Task<IEnumerable<T>> ListerParCritereAsync(Expression<Func<T, bool>> critere)
        {
            var filtre = new FilterDefinitionBuilder<T>().Where(critere);
            var resultat = await ObtenirCollection().FindAsync(filtre);
            return await resultat.ToListAsync();
        }

        public async Task<DonneesPage> ListerAvecPaginationEtTriAsync(Expression<Func<T, bool>> filtre, bool triAscendant, Expression<Func<T, object>> colonneTri, int page = 1, int taillePage = 20)
        {
            var donneesPage = new DonneesPage();
            await Task.Factory.StartNew(() =>
            {
                donneesPage.TaillePage = taillePage;
                donneesPage.NumeroPage = page;
                int aPasser = (page - 1) * taillePage;
                int aPrendre = taillePage;
                var requete = ObtenirCollection().AsQueryable();
                if (filtre != null)
                {
                    requete = requete.Where(filtre);
                }
                if (triAscendant)
                {
                    requete = requete.OrderBy(colonneTri);
                }
                else
                {
                    requete = requete.OrderByDescending(colonneTri);
                }
                donneesPage.TotalLignes = requete.Count();
                donneesPage.Donnees = requete.Skip(aPasser).Take(aPrendre).ToList();
            });
            return donneesPage;
        }

        public async Task<DonneesPage> ListerAvecPaginationAsync(Expression<Func<T, bool>> filtre, int page, int taillePage)
        {
            var donneesPage = new DonneesPage();
            await Task.Factory.StartNew(() =>
            {
                donneesPage.TaillePage = taillePage;
                donneesPage.NumeroPage = page;
                int aPasser = (page - 1) * taillePage;
                int aPrendre = taillePage;
                var requete = ObtenirCollection().AsQueryable();
                if (filtre != null)
                {
                    requete = requete.Where(filtre);
                }
                donneesPage.TotalLignes = requete.Count();
                donneesPage.Donnees = requete.Skip(aPasser).Take(aPrendre).ToList();
            });
            return donneesPage;
        }

        public async Task<List<T>> RechercherParDictionnaire(Dictionary<string, object> criteres)
        {
            var collection = ObtenirCollection();
            var filtre = new FilterDocument(criteres);
            var resultat = await collection.FindAsync(filtre);
            return await resultat.ToListAsync();
        }

        public void Modifier(T entite, ObjectId id)
        {
            var collection = ObtenirCollection();
            var documentBson = entite.ToBsonDocument();
            var filtre = new FilterDefinitionBuilder<BsonDocument>().Eq("_id", id);
            var miseAJour = new UpdateDefinitionBuilder<BsonDocument>().Set(documentBson);
            collection.UpdateOne(filtre, miseAJour);
        }

        public bool Modifier(T entite)
        {
            var filtre = new FilterDocument { { "codeUnique", "X100" } };
            var miseAJour = new UpdateDocument { { "$set", new FilterDocument { { "nom", "NouveauNom" } } } };
            var resultat = ObtenirCollection().UpdateOne(filtre, miseAJour);
            return resultat.ModifiedCount > 0;
        }
    }
}

Exemple de contrôleur API Web

using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace MonProjet.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class DonneesController : ControllerBase
    {
        private readonly IRepositoryMongo<Eleve> _repositorie;

        public DonneesController(IRepositoryMongo<Eleve> repositorie)
        {
            _repositorie = repositorie;
        }

        [HttpGet, Route("inserer")]
        public async Task<ResultatApi> InsererDonnees()
        {
            var resultat = new ResultatApi();
            try
            {
                var couleurs = new[] { "rouge", "bleu", "vert" };
                var listeEleves = new List<Eleve>();
                for (int i = 0; i < 2000000; i++)
                {
                    var aleatoire = new Random();
                    var eleve = new Eleve
                    {
                        Age = aleatoire.Next(18, 38),
                        DateNaissance = DateTime.Now.AddYears(-aleatoire.Next(12, 30)),
                        Nom = "eleve_" + aleatoire.Next(1000, 9999),
                        IdentifiantUnique = Guid.NewGuid().ToString(),
                        Livres = new List<Livre>
                        {
                            new Livre
                            {
                                Auteur = "auteur_" + i,
                                CodeLivre = Guid.NewGuid().ToString(),
                                Titre = "titre_" + i,
                                DateVente = DateTime.Now.AddYears(-aleatoire.Next(1, 30)),
                                PrixVente = aleatoire.Next(15, 125) * 1.0f,
                                Proprietes = new ProprietesLivre
                                {
                                    Quantite = aleatoire.Next(120, 350),
                                    Couleur = couleurs[aleatoire.Next(0, 3)],
                                    Hauteur = 25,
                                    Largeur = 18
                                }
                            }
                        }
                    };
                    listeEleves.Add(eleve);
                    if (listeEleves.Count >= 2000)
                    {
                        await _repositorie.InsererPlusieursAsync(listeEleves);
                        listeEleves.Clear();
                    }
                }
                if (listeEleves.Count > 0)
                {
                    await _repositorie.InsererPlusieursAsync(listeEleves);
                }
                resultat.Code = Statut.Succes;
                resultat.Message = "Insertion réussie.";
            }
            catch (Exception ex)
            {
                resultat.Message = "Erreur lors de l'insertion : " + ex.Message;
            }
            return resultat;
        }

        [HttpGet, Route("rechercher")]
        public async Task<ResultatApi> RechercherAvecPagination(int page = 1, int taillePage = 20)
        {
            var resultat = new ResultatApi();
            try
            {
                var donnees = await _repositorie.ListerAvecPaginationAsync(e => e.Livres[0].Proprietes.Couleur == "rouge", page, taillePage);
                resultat.Donnees = donnees;
                resultat.Message = "Recherche effectuée avec succès.";
                resultat.Code = Statut.Succes;
            }
            catch (Exception ex)
            {
                resultat.Message = "Erreur lors de la recherche : " + ex.Message;
            }
            return resultat;
        }
    }
}

Lors des tests d'écriture de quatre millions d'enregistrements, la vitesse moyenne s'est située entre 12000 et 15000 documents par seconde, sur une infrastructure minimale avec un processeur monocœur et 1 Go de mémoire vive.

Étiquettes: MongoDB .NET Core 3.1 C# API Web Docker

Publié le 7 juin à 17h58