NHibernateEg.Tutorial1A

Introduction progressive au Mapping Objet / Relationnel et mise en œuvre avec une application console utilisant NHibernate

Pierre Henry Kuaté

Cet article présente brièvement le Mapping Objet / Relationnel (en anglais: Object / Relational Mapping); une technique permettant d'utiliser les principes de la programmation orientée objet et des bases de données relationnelles en faisant un minimum de compromis. Une application console est définie pour effectuer quelques opérations de base. Et son implémentation utilise NHibernate afin de montrer concrètement ce qu'est le Mapping Objet / Relationnel et comment s'en servir.

N'hésitez pas à commenter cet article ! Commentez ♪

- Titre : NHibernateEg.Tutorial1A

- Auteur : Pierre Henry Kuaté

- Parution : 12 janvier 2006

- Mise à jour : 11 janvier 2013

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2013 Pierre Henry Kuaté. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Obtenir le tutoriel et l'application NHibernateEg

- II. Prérequis

- III. Mapping orienté objet / Relationnel

- IV. Conventions de ce document

- V. Présentation du tutoriel

- VI. Présentation de la Commande et de la Boutique

- VII. Examen rapide de l'approche ADO.NET

- VIII. Utilisation du Mapping orientée objet / Relationnel et de NHibernate

- IX. Implémentation de Commande et introduction à Nhibernate.Mapping.Attributes

- IX-A. L'en-tête de la classe

- IX-B. L'identificateur et son générateur

- IX-C. Les propriétés

- IX-D. Les méthodes

- IX-E. Quelques remarques

- X. Configuration de NHibernate (base de données et informations de liaison)

- XI. API de persistance

- XII. Opérations CRUD

- XII-A. CREATE: Créer et enregistrer

- XII-B. RETRIEVE : Requête et Chargement

- XII-C. UPDATE: SaveOrUpdate / Save / Update

- XII-D. DELETE : Détruire(HQL) et Détruire(Entité)

- XIII. Conclusion

I - Obtenir le tutoriel et l'application NHibernateEg

Vous pouvez lire tous les tutoriels en ligne ici: https://kpixel.developpez.com/NHibernateEg/. Une version en anglais est disponible ici: http://nhibernate.sourceforge.net/NHibernateEg/.

Le code source (avec l'exécutable et ce tutoriel) peut être téléchargé ici:  ftp://ftp-developpez.com/kpixel/NHibernateEg.zip (ou  http://kpixel.ftp-developpez.com/NHibernateEg.zip).

Étant donné que ce tutoriel est assez long, il peut demander beaucoup de temps pour être parfaitement compris. Je vous conseille de faire autant de pratique que possible; testez chaque fonctionnalité en créant de petites applications.

II - Prérequis

Afin de bien comprendre ce tutoriel, je considère que vous avez des connaissances basiques du développement d'applications utilisant une base de données avec le framework .NET. C'est-à-dire, travailler avec un système de bases de données relationnelles basé sur le SQL (RDBMS), créer une connexion vers ce système dans une application .NET, effectuer des opérations basiques et manipuler des transactions.

Étant donné que le code source est écrit en C#, vous devez être capable de comprendre ce langage.

L'application nécessite le framework .NET 1.1 pour être exécutée.

Afin de modifier le code source, vous avez besoin du SDK de .NET et d'un EDI comme  NAnt  SharpDevelop ou Visual Studio .NET.

Vous devez évidemment savoir vous servir de votre EDI. La solution VS .NET 2003 de ce tutoriel est le fichier /src/NHibernateEg.sln. Le combine SharpDevelop de ce tutoriel est le fichier /src/NHibernateEg.cmbx. Vous pouvez aussi compiler le code source en lançant le fichier /src/Compiler-dans-bin.bat (assurez que NAnt est correctement installé).

Par défaut, cette application utilise une base de données Microsoft Access (incluse dans l'archive zip). Donc, vous ne devriez pas avoir à configurer l'accès à la base de données. Mais il est aisé de modifier la configuration pour utiliser une base de données MySQL ou SQL Server (ou MSDE). En fait, il est possible d'utiliser n'importe quelle RDBMS supportée par NHibernate, mais vous pourrez avoir à faire plus de modifications.
Vous pouvez aussi télécharger NHibernate (et NHibernateContrib) sur  son site SourceForge. Ces archives contiennent ses fichiers binaires, son code source et sa documentation. Mais ce n'est pas indispensable pour ce tutoriel.

III - Mapping orienté objet / Relationnel

Un logiciel de Mapping Objet / Relationnel est une couche de persistance connectant les objets d'un système orienté objet à des données stockées dans une base de données relationnelle. L'usage du Mapping Objet / Relationnel (MOR) (ORM en anglais) permet d'appliquer une analyse et une modélisation orientée objet proprement en cachant les spécificités liées à l'usage d'un système relationnel.

Le MOR est très populaire dans le monde Java; et même s'il est assez peu connu dans le monde .NET, il existe déjà beaucoup de logiciels (commerciaux ou libres).

Ne confondez pas le Mapping Objet / Relationnel (en anglais: Object / Relational Mapping) avec la Modélisation Objet / Rôle (en anglais: Object / Role Modeling) qui est une méthode « orientée fait » pour effectuer des analyses d'information au niveau conceptuel.

Il existe plusieurs types de logiciels de Mapping Objet / Relationnel :

- Purement Relationnel : pas de MOR ici; l'application utilise des tables et des lignes (avec des DataSets et/ou des DataReaders) ;

- Mapping Objet Faible : les requêtes SQL sont isolées/encapsulées autant que possible et certaines lignes de tables sont manuellement converties en objets ;

- Mapping Objet Intermédiaire : l'application est conçue avec des objets. Les associations et collections peuvent être mappées. Et il est possible d'exécuter des requêtes sur des objets ;

- Mapping Objet Total : solution plus avancée que le Mapping Objet Intermédiaire; elle permet des liaisons complexes comme l'héritage. Elle a une API pour exécuter des requêtes complètes et orientées objet et des fonctions avancées comme la gestion de la concurrence et des stratégies pour mettre les données en cache.

Un « Mapper » Objet / Relationnel utilise en interne plusieurs patterns et fonctionnalités .NET comme : L'Unité De Travail, la « Carte d'Identité » (en anglais: Identity Map), le Chargement En Retard (en anglais: Lazy Loading), la Réflexion, le XML, etc.

Pour plus de détails sur le MOR, lisez :

- Articles très intéressants sur le Mapping Objet / Relationnel (sur DotNetGuru.org)

- Les Fondations du Mapping Objet / Relationnel (par Mark L. Fussell)

- Introduction au Mapping Objet / Relationnel (sur le blog de Fredrik NMORén's)

- Mapping orienté objet / Relationnel (sur le wiki c2.com)

- « Patterns of Enterprise Application Architecture » (sur le site web de Martin Fowler)

NHibernate permet le Mapping Objet Total.

De   « NHibernate - Project du Mois, novembre 2005 » sur SourceForge.net :

NHibernate est le portage de l'excellent  Hibernate, outil de persistance relationnel vers la plate-forme .NET. Hibernate est une solution dominante pour le mapping objet / relationnel (MOR) et la persistance d'objets en général pour la plate-forme Java. Le logiciel de persistance d'objets pour bases de données relationnelles NHibernate (version 1.0) est pratiquement compatible à 100 % (en termes de fonctionnalités) avec Hibernate 2.1. Ce qui rend NHibernate et Hibernate uniques, c'est leur approche de la persistance, dans laquelle vos objets n'ont pas à hériter d'une classe de base quelconque ou d'implémenter d'une interface, (N)Hibernate fonctionne avec des objets classiques (en anglais : plain old Java/CLR objects). Hibernate est aussi extrêmement flexible – vous pouvez définir comment persister chaque propriété et relation, vous pouvez aussi effectuer des requêtes en utilisant un langage de requête très concis et puissant, etc.

À qui est destiné ce logiciel ? Tout développeur créant des applications d'entreprise sur la plate-forme .NET, avec un modèle de données complexe. Toute personne ayant besoin d'un bon outil de MOR, et toute personne qui ne sait pas encore ce qu'est le MOR. Tout développeur .NET qui travaille sur des applications centrées sur un modèle de données sauvegardées dans un RDBMS.

NHibernate est prêt pour un usage en entreprise, la version 1.0 est sortie en octobre 2005. Et il est supporté par  JBoss Inc.

IV - Conventions de ce document

Ce tutoriel n'est pas régi par des conventions très strictes. Le but est de mettre en évidence les phrases / mots importants.

Conventions typographiques

Convention

Exemple

Phrases / mots en gras

Cette fonctionnalité s'appelle la persistance transparente

Nom de fichier

Le fichier exécutable NHibernateEg.Tutorial1A.exe

Nom de classe/méthode/attribut .NET (et tout autre code C# ou XML)

La classe Boutique utilise les informations de <appSettings>

V - Présentation du tutoriel

Ce tutoriel est une introduction à NHibernate pour les nouveaux utilisateurs.

Ici, vous découvrirez comment configurer NHibernate et comment l'utiliser. Nous allons créer une Application Console non interactive pour effectuer quelques opérations basiques.

Vous devez avoir tous les prérequis et le code source (ou au moins l'exécutable).

VI - Présentation de la Commande et de la Boutique

L'application de ce tutoriel mime une (très simple) boutique. La principale (et unique) classe gérée est la Commande.

Commande est une classe qui a un identificateur qui rend chaque instance unique dans la base de données.

Ses propriétés sont :

- une Date permettant de savoir quand la commande a été créée ;

- le nom du produit commandé (pour plus de simplicité, une commande ne peut faire référence qu'à un produit et c'est sont nom qui est enregistré) ;

- la quantité d'éléments (du produit) commandés ;

- le prix total de cette commande (c'est-à-dire: quantité * prix unitaire).

Boutique est une classe qui peut être utilisée pour effectuer quelques opérations liées à Commande.

Vous pouvez demander à la boutique de :

- Générer quelques commandes aléatoires; c'est utile pour avoir des données à manipuler dans la base de données ;

- Imprimer quelques champs de toutes les commandes dans la base de données ;

- Charger une commande : récupère la ligne dans la base de données et la convertit en une instance de la classe Commande ;

- Enregistrer/Mettre à jour/Effacer une commande (dans la base de données).

Maintenant, lancez le fichier exécutable NHibernateEg.Tutorial1A.exe pour voir toutes ces opérations en action.

Avant de lancer cette application

Vous pouvez avoir à configurer l'accès à votre base de données. Ouvrez le fichier NHibernateEg.Tutorial1A.exe.config. Par défaut, cette application utilise une base de données Microsoft Access dont le nom est nhibernate.mdb. La connexion est réalisée en utilisant le Microsoft Jet 4.0 Database Engine.

Si vous voulez utiliser une base de données Microsoft SQL Server 2000 (ou MSDE), changez la ligne: <add key=« BaseDeDonnées » value=« Access » /> à <add key=« BaseDeDonnées » value=« MSSQL » />.

Si vous voulez utiliser une base de données MySQL, changez la ligne: <add key=« BaseDeDonnées » value=« Access » /> à <add key=« BaseDeDonnées » value=« MySQL » />. Et assurez-vous que les fichiers MySql.Data.dll et ICSharpCode.SharpZipLib.dll sont dans votre chemin (ils devraient déjà être dans le répertoire courant).

Vous pouvez aussi changer la chaîne de connexion de cette base de données si nécessaire. Modifier le champ value de: Access.ChaîneDeConnexion (pour Microsoft Access), MSSQL.ChaîneDeConnexion (pour SQL Server) ou MySQL.ChaîneDeConnexion (pour MySQL).

Remarquez que vous devez juste créer la base de données et en configurer l'accès; l'application se chargera de la remplir (après avoir détruit toute table entrant en conflit).

Lorsque vous lancez cette application, vous devriez obtenir quelque chose similaire à :

Capture d'écran

L'application démarre...
Configuration de NHibernate...
Utilise la base de données: <Access>
ChaîneDeConnexion: <Provider=Microsoft.Jet.OLEDB.4.0;Data Source=nhibernate.mdb>

NHibernate.Mapping.Attributes.HbmSerializer.Default.Serialize()...
new NHibernate.Tool.hbm2ddl.SchemaExport(cfg).Create()...
drop table SimpleCommande
Unsuccessful: La table 'SimpleCommande' n'existe pas.
create table SimpleCommande (Id INT !!! REPLACE THE PRECEEDING 'INT' AND THIS
PLACEHOLDER WITH 'COUNTER' !!!, `Date` DATETIME null, Produit TEXT(255) not null,
Quantité INT null, PrixTotal INT null, primary key (Id))

sessionFact = cfg.BuildSessionFactory();

Enregistrement de 3 commandes aléatoires...
3 commandes trouvées!
Commande N°1,  Date=2005-12-25 22:55:40Z,  Produit=P1
Commande N°2,  Date=2005-12-25 22:55:41Z,  Produit=P2
Commande N°3,  Date=2005-12-25 22:55:41Z,  Produit=P3
Chargement de la commande N° 1...
Commande N°1
Date = dimanche 25 décembre 2005 21:55:40
Produit=P1 (modifié),  Quantité=3,  PrixTotal=9
Mise à jour la commande N° 1...
Insertion la commande N° 0...
Changement du fuseau horaire de toutes les commandes: n=25...
4 commandes modifiées!
Effacement de la commande N° 2...
3 commandes trouvées!
Commande N°1,  Date=2005-12-26 23:55:40Z,  Produit=P1 (modifié)
Commande N°3,  Date=2005-12-26 23:55:41Z,  Produit=P3
Commande N°4,  Date=2005-12-26 23:55:41Z,  Produit=Nouveau
L'application est fermée!

Si vous obtenez une exception, lisez-la pour comprendre le problème exact (il est probablement lié à l'installation de votre base de données ou votre chaîne de connexion).

Vous pouvez ignorer, en toute sécurité, les messages juste après l'appel de la méthode SchemaExport(cfg).Create() tant qu'il ne s'agit pas d'exceptions.

Étudiez cette capture d'écran pour comprendre ce que nous allons réaliser dans les prochaines sections.

Voici le code du point d'entrée de l'application (la méthode Programme.Main()) qui a généré cette capture d'écran :

string baseDeDonnées = System.Configuration.ConfigurationSettings.AppSettings["BaseDeDonnées"];
string chaîneDeConnexion = System.Configuration.ConfigurationSettings.AppSettings[baseDeDonnées + ".ChaîneDeConnexion"];
Boutique boutique = new Boutique(baseDeDonnées, chaîneDeConnexion);
boutique.GénèreDesCommandesAléatoires(3);
boutique.AfficheToutesLesCommandes();
Commande c = boutique.ChargeUneCommande(1);
c.Produit += " (modifié)";
boutique.Affiche(c);
boutique.Sauvegarde(c);
boutique.Sauvegarde( new Commande("Nouveau", 4, 2) );
boutique.ChangeLeFuseauHoraire(25);
boutique.Détruit(2);
boutique.AfficheToutesLesCommandes();

Les chaînes de caractère baseDeDonnées et chaîneDeConnexion sont extraites du fichier NHibernateEg.Tutorial1A.exe.config et envoyé à la Boutique pour sa configuration.

Ensuite, nous effectuons quelque opérations CRUD avec des Commandes: Créer, Récupérer, Update (mettre à jour) et Détruire. Leurs implémentations montreront l'usage de NHibernate pour des opérations basiques.

VII - Examen rapide de l'approche ADO.NET

Si vous connaissez ADO.NET assez bien, vous pouvez réaliser l'application que nous venons de décrire en suivant ces étapes :

- création des tables dans la BD + DataSet typé (peut être généré et optionnel) ;

- création de DbConnection + DataAdapters + Commands (peuvent être généré) ;

- cycle de vie de l'application (=> implémentation des opérations CRUD).

Cette méthode a quelques désavantages (qui s'amplifient avec la complexité du logiciel) :

- manque de flexibilité / Maintenance difficile ;

- difficile de correctement appliquer la Programmation Orientée Objet (POO) ;

- difficile de séparer le modèle de données (en anglais: Domain Model) de la base de données.

VIII - Utilisation du Mapping orientée objet / Relationnel et de NHibernate

Après la présentation théorique faite dans la section « Mapping Objet / Relationnel », nous allons nous concentrer sur l'aspect pratique.

Un logiciel de Mapping Objet / Relationnel permet de se concentrer sur le modèle objet lorsqu'on conçoit une application. La base de données est abstraite par le framework de persistance.

Tout au long de l'implémentation de la Boutique, vous constaterez que nous ne parlerons quasiment pas de base de données (à part pour en configurer l'accès) et nous ne manipulerons pas de tables/lignes ou de requêtes SQL.

Vous allez découvrir les avantages de cette technologie en l'utilisant.

L'étape zéro est de créer une Application Console et d'ajouter les bibliothèques: NHibernate.dll, log4net.dll et NHibernate.Mapping.Attributes.dll comme références.

IX - Implémentation de Commande et introduction à Nhibernate.Mapping.Attributes

Maintenant, nous allons implémenter la classe Commande. C'est-à-dire : Créer la classe, ajouter les champs / propriétés et les méthodes (dans un fichier C#).

Pour manipuler des classes, NHibernate a besoin d'une liaison (en anglais: mapping) entre ces classes et les tables de la base de données. Ici, nous utilisons la bibliothèque NHibernate.Mapping.Attributes pour fournir ces informations.

NHibernate.Mapping.Attributes utilise des attributs .NET pour définir la liaison.
En gros, pour chaque élément de vos classes, vous appliquez le bon attribut de sorte que NHibernate sache comment cet élément est lié à son équivalent dans la base de données. Chaque attribut peut avoir plusieurs propriétés utilisées pour spécifier comment la liaison doit fonctionner. Lisez  la documentation de référence de NHibernate sur ces liaisons pour comprendre leurs significations.

Voici l'implémentation de la classe Commande :

IX-A - L'en-tête de la classe

Commande - En-tête

[NHibernate.Mapping.Attributes.Class(Table="SimpleCommande")]
public class Commande
{
}

L'attribut .NET [Class] est utilisé pour dire à NHibernate que c'est une classe liée (à une table). Avec Table=« SimpleCommande », nous spécifions le nom de la table dans lesquelles les commandes sont enregistrées.

Aucune interface spéciale n'a à être implémentée et vous n'avez pas à hériter d'une classe mère de persistance. NHibernate n'exige aucun traitement au moment de la compilation, il ne compte que sur une fonctionnalité de .NET appelée : réflexion.

IX-B - L'identificateur et son générateur

Commande - Identificateur et générateur

private int _id = 0;
[NHibernate.Mapping.Attributes.Id(Name="Id")]
[NHibernate.Mapping.Attributes.Generator(1, Class="native")]
public virtual int Id
{
get { return _id; }
}

L'identificateur est défini avec attribut [Id]. N'oubliez pas de définir son nom (celui-ci ne peut être deviné).

L'identificateur peut être assigné soit par la base de données, soit par l'application (vous) soit par NHibernate. Lisez la documentation pour plus de détails sur ces options. L'attribut [Generator] est utilisé pour choisir l'une de ces stratégies. Nous laissons la base de données choisir l'identificateur, c'est pourquoi nous écrivons Class=« native ».

Il n'y a pas de set { … } parce que l'Id ne devrait jamais être changé (seul NHibernate le change lorsqu'il est chargé/sauvegardé). Vous pourriez en avoir besoin si vous choisissez une stratégie de génération qui force l'affectation manuelle de l'identificateur.

Comme vous pouvez le voir, il y'a deux attributs sur cette propriété (et il peut y en avoir encore plus). [Generator] est indenté pour mettre en évidence le fait qu'il appartient à [Id]; et « 1 » (ou Position=1) précise qu'il vient après [Id] (dont la position est « 0 »); les attributs .NET ne sont pas automatiquement ordonnés.

IX-C - Les propriétés

Commande - Les propriétés

private System.DateTime _date = System.DateTime.Now;
private string _produit;
private int _quantité;
private int _prixTotal;

[NHibernate.Mapping.Attributes.Property(Column="`Date`")]
public virtual System.DateTime Date
{
get { return _date; }
}

[NHibernate.Mapping.Attributes.Property(NotNull=true)]
public virtual string Produit
{
get { return _produit; }
set { _produit = value; }
}

[NHibernate.Mapping.Attributes.Property]
public virtual int Quantité
{
get { return _quantité; }
set { _quantity = value; }
}

[NHibernate.Mapping.Attributes.Property]
public virtual int PrixTotal
{
get { return _prixTotal; }
set { _prixTotal = value; }
}

[Property] est utilisé pour les champs qui sont directement liés à des colonnes dans la base de données. Le type de la propriété et de la colonne doivent être compatibles.

Étant donné que « Date » est un mot réservé dans la plupart des RDBMS, nous devons mettre le nom de sa colonne entre guillemets; ce qui est fait avec : Column=« `Date` ».

NotNull=true est écrit parce que le nom du produit ne doit pas être nul. Si vous voulez avoir des propriétés pouvant prendre la valeur « nul » (pour les bool/int/float/DateTime/…), vous pouvez utiliser la bibliothèque  Nullables (elle est distribuée dans l'archive NHibernateContrib).

Si vous vous demandez comment NHibernate peut modifier des propriétés en lecture seule, vous verrez que NHibernate est configuré pour utiliser les champs privés. L'avantage de cette fonctionnalité est que vous pouvez utiliser les noms des propriétés dans les requêtes et étant donné que NHibernate accède directement aux champs, il n'exécute pas l'éventuel code contenu dans les propriétés. En fait, NHibernate peut utiliser des champs/propriétés publics/protégés/privés.

Tous vos membres de classe (propriétés, méthodes et événements) qui accèdent aux champs directement doivent être virtual. C'est nécessaire lorsque le chargement à retardement (en anglais: lazy loading) est activé sur la classe (ce qui sera le cas dans le prochain tutoriel).

Donc, si vous avez un membre qui n'est pas virtuel (ou qui n'appartient pas à cette classe) et qui doit accéder à un champ, il devrait passer par un membre virtuel de la classe (directement ou pas); la plupart du temps, en utilisant pas propriété virtuelle de ce champ.

IX-D - Les méthodes

Commande - Les méthodes

public Commande()
{
}

public Commande(string produit, int prixUnitaire, int quantité)
{
this.Produit = produit;
this.Quantité = quantité; // Ne pas utiliser _quantité
this.CalculeLePrixTotal(prixUnitaire);
}

public void CalculeLePrixTotal(int prixUnitaire)
{
this.PrixTotal = prixUnitaire * this.Quantité;
}

public virtual void ChangeLeFuseauHoraire(int n)
{
this._date = this.Date.AddHours(n);
}

Comme vous pouvez le voir, les champs ne sont jamais utilisés directement; la seule exception étant ChangeLeFuseauHoraire() qui est virtual.

La classe Commande a un constructeur ne prenant aucun paramètre (public Commande()). Il est nécessaire pour permettre à NHibernate de créer des instances en interne. Mais ce constructeur peut être protected et même private si le chargement à retardement n'est pas activé.

IX-E - Quelques remarques

Si vous ne définissez pas (dans les attributs) le nom d'une table/colonne dans laquelle la classe/propriété est chargé/sauvegardé, NHibernate devinera qu'elles ont le même nom.

Pour comprendre un peu mieux NHibernate.Mapping.Attributes, vous pouvez lire  sa documentation. Et la documentation de NHibernate contient une explication pour chaque liaison.

En lisant d'autres articles/codes source, vous vous rendrez compte que les informations de liaison sont stockées dans des fichiers .hbm.xml. En fait, utiliser les attributs est une méthode plus simple et plus claire pour générer ces informations (elle est aussi beaucoup moins verbeuse). Il est néanmoins possible que vous ayez à éditer ces fichiers « à la main ». Et ne vous inquiétez pas, il est possible d'utiliser ces deux techniques dans une même application; il est même possible d'enregistrer les informations des attributs dans des fichiers .hbm.xml.

X - Configuration de NHibernate (base de données et informations de liaison)

Cette configuration est réalisée dans le constructeur de Boutique: public Boutique(string baseDeDonnées, string chaîneDeConnexion).

Avant de configurer NHibernate, il y a une bibliothèque que vous devez connaître : log4net.

log4net est un système d'enregistrement d'informations de débogage. En gros, il reçoit des messages (en anglais: logs), il les filtre comme vous voulez et il les envoie où vous voulez (dans un fichier, sur la console, dans une base de données, par courriel, etc.).

Sa configuration est dans le fichier NHibernateEg.Tutorial1A.exe.config, dans la section appelée <log4net>. Vous devez appeler une des méthodes log4net.Config.XmlConfigurator.Configure() avant de l'utiliser; ces méthodes lisent la configuration.

En fin, vous mettez quelque part dans votre code :

Configuration de log4net

[assembly: log4net.Config.XmlConfigurator(Watch=true)]

Watch=true dit à log4net de détecter tout changement fait dans le fichier de configuration pendant l'exécution de l'application (très pratique pour ne pas avoir à redémarrer l'application…). Pour plus de détails, allez sur le site web de log4net.

NHibernate a besoin de quelques informations pour savoir comment communiquer avec la base de données :

- ConnectionProvider : NHibernate.Connection.DriverConnectionProvider ;

- Dialect et ConnectionDriver : ils dépendent de votre base de données. ;

- ConnectionString : la chaîne de caractères utilisée pour créer la connexion à la base de données.

La section <appSettings /> (dans NHibernateEg.Tutorial1A.exe.config) permet d'aller d'une base de données à une autre sans changer une seule ligne de code. Réaliser cela avec du pur ADO.NET n'est pas évident.

Code utilisé pour définir ces informations

// Crée l'objet qui contiendra les informations de configuration
// et assigne les informations pour accéder à la base de données
NHibernate.Cfg.Configuration cfg = new NHibernate.Cfg.Configuration();
cfg.SetProperty(NHibernate.Cfg.Environment.ConnectionProvider, "NHibernate.Connection.DriverConnectionProvider");

if("Access" == baseDeDonnées)
{
cfg.SetProperty(NHibernate.Cfg.Environment.Dialect, "NHibernate.JetDriver.JetDialect, NHibernate.JetDriver");
cfg.SetProperty(NHibernate.Cfg.Environment.ConnectionDriver, "NHibernate.JetDriver.JetDriver, NHibernate.JetDriver");
cfg.SetProperty(NHibernate.Cfg.Environment.ConnectionString, chaîneDeConnexion);
}
else [...]

Ici, nous pouvons lire les valeurs à utiliser pour accéder à une base de données Microsoft Access. Si vous voulez utiliser un autre RDBMS, vous devez trouver son Dialect et son ConnectionDriver (lisez la documentation de NHibernate). Ces valeurs sont définies avec la méthode SetProperty(). Elles peuvent aussi être définies grâce à un fichier XML (cette méthode est utilisée dans le prochain tutoriel).

Extraction des informations de liaison avec NHibernate.Mapping.Attributes

System.IO.MemoryStream flux = new System.IO.MemoryStream(); // Contenant des informations
NHibernate.Mapping.Attributes.HbmSerializer.Default.Validate = true; // Active la validation (optionnel)
// Demande à NHibernate d'utiliser les champs et non les propriétés (dans les entités)
NHibernate.Mapping.Attributes.HbmSerializer.Default.HbmDefaultAccess = "field.camelcase-underscore";
// Récupère les informations à partir de cette assemblée (peut aussi être fait classe par classe)
System.Console.Out.WriteLine("NHibernate.Mapping.Attributes.HbmSerializer.Default.Serialize()...\n");
NHibernate.Mapping.Attributes.HbmSerializer.Default.Serialize(flux,
System.Reflection.Assembly.GetExecutingAssembly());
flux.Position = 0;
cfg.AddInputStream(flux); // Envoi les informations de Mappage à la Configuration de NHibernate
flux.Close();

Avec HbmDefaultAccess = « field.camelcase-underscore », NHibernate convertira le nom de la propriété en camel case et ajoutera un souligné (« _ ») au début pour obtenir le nom du champ qui contient la donnée. Lisez la documentation pour découvrir les autres stratégies d'appellation. Si le champ est privé, il utilisera la réflexion (donc, assurez-vous que le niveau de sécurité de l'application permet cela).

Dans la méthode Serialize(), NHibernate.Mapping.Attributes utilise la réflexion de .NET pour trouver toutes les classes (dans la ExecutingAssembly) avec l'attribut [Class] et remplit ce flux avec les informations sur ces classes.

Une fois que le flux est rempli, on le rembobine et on l'envoie à l'instance de configuration de NHibernate en utilisant la méthode AddInputStream(). Elle utilisera le contenu XML, amassera toutes les informations dont NHibernate aura besoin pour manipuler ces classes.

Utilisation de SchemaExport

new NHibernate.Tool.hbm2ddl.SchemaExport(cfg).Create(true, true);

Ici, nous créons une instance de SchemaExport et nous appelons directement la méthode Create(). Elle utilise les informations fournies (dans l'instance de configuration) pour générer et exécuter un script qui créera les tables et les relations dans la base de données pour contenir les classes mappées. Remarquez qu'elle n'arrivera pas à effacer une table si cette table a une référence qui n'existe pas dans les informations de liaison; dans ce cas, vous devez effacer la table manuellement.

Création de la SessionFactory

_sessionFactory = cfg.BuildSessionFactory();

Enfin, nous créons la SessionFactory (explication dans la prochaine section). Remarquez que _sessionFactory est une instance de l'implémentation de l'interface ISessionFactory. Et il sera seulement détruit à la fermeture de l'application.

XI - API de persistance

Avant de se lancer dans NHibernate, il y a quelques concepts à expliquer.

La notion d'« Entité » : dans cet article, une entité est simplement une classe dont les informations de liaison ont été envoyées à NHibernate. Donc, NHibernate connaît cette classe et peut la manipuler.

L'interface principale utilisée pour manipuler les entités est ISession (nom complet: NHibernate.ISession).

Vous avez besoin d'une ISessionFactory pour chaque base de données. En général, elle est créée à l'initialisation de l'application comme c'est le cas ici. Son implémentation est permet son utilisation par plusieurs threads (en anglais: thread-safe) et même après qu'une exception soit lancée (en anglais: exception-proof), donc elle peut vivre aussi longtemps que votre application.

Une session est créée par une ISessionFactory grâce à la méthode ISessionFactory.OpenSession().

Vous devriez utiliser le pattern session-par-requête; c'est-à-dire créer une session pour chaque requête utilisateur. C'est conseillé parce qu'une session n'est pas exception-proof (ni thread-safe). Si une exception est lancée, vous devez annuler les modifications grâce à la transaction et fermer la session (n'essayez pas de la récupérer).

Utilisation classique d'une session (et de sa transaction)

NHibernate.ISession session = null;
NHibernate.ITransaction transaction = null;
try
{
session = _sessionFactory.OpenSession();
transaction = session.BeginTransaction();

// Opérations CRUD ici (avec la session)

transaction.Commit();
}
catch
{
if(transaction != null)
transaction.Rollback();
throw;
}
finally
{
if(session != null)
session.Close();
}

Comme vous pouvez le lire dans ce code, nous créons la session, puis sa transaction. Nous utilisons la session pour faire quelques opérations CRUD; enfin, nous envoyons les modifications et nous fermons la session (annuler les modifications si une exception est lancée).

Les transactions de NHibernate se comportent comme celles d'ADO.NET. La transaction est étroitement liée à la session. Les commandes SQL ne sont générées et exécutées qu'à l'appel de transaction.Commit(). Mais il est possible de forcer cela en appelant session.Flush().

Remarquez que créer la transaction peut être optionnel (principalement lorsque vous voulez juste exécuter des opérations SELECT), dans ce cas, vous pouvez écrire: (using() vas automatiquement fermer la session).

Utilisation simplifiée d'une session

using(NHibernate.ISession session = _sessionFactory.OpenSession())
{
// Récupération de données ici (avec la session)
}

Une entité a un état qui peut changer : l'état que vous connaissez implicitement est transient (en anglais). Lorsque vous créez une instance de classe, cette instance est transient.

Une fois que vous avez envoyé cette instance à une session (pour l'enregistrer), cette instance devient persistée. À partir de là, la session a cette instance dans son cache.

Si vous effacez cette instance, elle n'est évidemment pas détruite dans la mémoire; la session effacera sa ligne dans la base de données et l'enlèvera de son cache (ce qui signifie qu'elle redevient transient). Il faut noter qu'une entité ne peut pas être attachée à deux sessions ouvertes à même temps.

Enfin, lorsqu'une instance est persistée et que la session qui l'a persisté est fermée, cette instance devient détachée. La différence avec transient est qu'il peut y avoir des informations utiles cachées dans cette instance et qui pourront être utilisées par une autre session.

XII - Opérations CRUD

Maintenant, nous allons utiliser des sessions pour effectuer quelques opérations CRUD de base (c'est-à-dire Créer, Récupérer, Update (mettre à jour), Détruire).

Pour chaque opération, une méthode sera affichée et expliquée.

XII-A - CREATE: Créer et enregistrer

Implémentation de la méthode Boutique.GénèreDesCommandesAléatoires() :

Création et enregistrement de 'n' commandes aléatoires

public void GénèreDesCommandesAléatoires(int n)
{
NHibernate.ISession session = null;
NHibernate.ITransaction transaction = null;

System.Console.Out.WriteLine("\nEnregistrement de " + n + " commandes aléatoires...");
try
{
session = _sessionFactory.OpenSession();
transaction = session.BeginTransaction();

for(int i=0; i<n; i++)
{
Commande c = new Commande();

c.Produit = "P" + (i+1).ToString();
c.Quantité = n - i;
c.CalculeLePrixTotal(i * 10 + n);

session.Save(c);
}

// Envoie les modifications (=> Génère et exécute les requêtes)
transaction.Commit();
}
catch
{
if(transaction != null)
transaction.Rollback(); // Erreur => nous DEVONT annuler les modifications
throw; // Ici, nous renvoyons la même exception pour qu'elle soit gérée (imprimée)
}
finally
{
if(session != null)
session.Close();
}
}

Dans cette méthode, les commandes sont créées et remplies avec des données aléatoires. Ensuite, la méthode session.Save() est utilisée pour les enregistrer.

XII-B - RETRIEVE : Requête et Chargement

Pour récupérer des entités, NHibernate a deux API de requête :

- le Langage de Requête Hibernate (en anglais: Hibernate Query Language ou HQL). C'est un langage similaire au SQL, mais « orienté objet » ;

- l'API de Requête par Critères qui fournit des classes pour formuler des requêtes typées.

Ces API retournent en résultat une IList qui contient les éléments conformes aux critères.

Lorsque vous voulez charger une entité (dont vous connaissez l'identificateur), vous pouvez utiliser session.Load() ou session.Get(), envoyez le type et l'identificateur de l'entité pour la charger.

session.Load() lance une exception si aucune entité n'est trouvée avec cet identificateur et session.Get() retourne simplement null dans ce cas. La session exécute une requête pour récupérer la ligne, crée une instance, la remplit et la retourne.

Implémentation de la méthode Boutique.AfficheToutesLesCommandes() :

Pour chaque commande, impression de son identificateur, sa date et le nom du produit

public void AfficheToutesLesCommandes()
{
using(NHibernate.ISession session = _sessionFactory.OpenSession())
{
System.Collections.IList résultat = session.Find("select c.Id, c.Date, c.Produit from Commande c");

System.Console.Out.WriteLine("\n" + résultat.Count + " commandes trouvées!");
foreach(System.Collections.IList l in résultat)
System.Console.Out.WriteLine("  Commande N°"
+ l[0] + ",  Date=" + ((System.DateTime)l[1]).ToString("u") + ",  Produit=" + l[2]);
}
}

Ici, il n'y a pas de transaction (inutile) et using() fermera la session à la fin. La requête HQL exécutée ici est « select c.Id, c.Date, c.Produit from Commande c ». Cette requête est identique à celle qui serait écrite en SQL. Nous demandons une liste de quelques éléments (l'Id, la Date et le Produit) dans la table des commandes. Ces éléments sont stockés dans une IList donc nous obtenons une IList contenant des ILists.

Pour construire des requêtes plus complexes, vous pouvez utiliser une instance de IQuery en utilisant la méthode ISession.CreateQuery(); elle prend la requête HQL et retourne une instance qui expose des fonctionnalités supplémentaires. Et il existe quelques autres alternatives. Lisez la documentation pour plus de détails.

Implémentation de la méthode Boutique.ChargeUneCommande() :

Renvoi de la commande N° 'id'

public Commande ChargeUneCommande(int id)
{
System.Console.Out.WriteLine("\nChargement de la commande N° " + id + "...");
using(NHibernate.ISession session = _sessionFactory.OpenSession())
return session.Load(typeof(Commande), id) as Commande;
}

Cette méthode utilise session.Load() parce que nous savons qu'il serait exceptionnel que l'entité à charger n'existe pas.

XII-C - UPDATE: SaveOrUpdate / Save / Update

Implémentation de la méthode Boutique.Sauvegarde() :

Insertion ou mise à jour de la commande (dans la base de données)

public void Sauvegarde(Commande c)
{
NHibernate.ISession session = null;
NHibernate.ITransaction transaction = null;

// Voici comment la Session décide d'insérer ou de mettre à jour;
// utilisez NHMA.Id(UnsavedValue=x) pour remplacer 0
System.Console.Out.Write("\n"  +  (c.Id == 0  ?  "Insertion"  :  "Mise à jour"));
System.Console.Out.WriteLine(" la commande N° " + c.Id + "...");
try
{
session = _sessionFactory.OpenSession();
transaction = session.BeginTransaction();

session.SaveOrUpdate(c);

// Envoie les modifications (=> Génère et exécute les requêtes)
transaction.Commit();
}
catch
{
if(transaction != null)
transaction.Rollback(); // Erreur => nous DEVONT annuler les modifications
throw; // Ici, nous renvoyons la même exception pour qu'elle soit gérée (imprimée)
}
finally
{
if(session != null)
session.Close();
}
}

NHibernate utilise une technique particulière pour savoir s'il doit émettre des commandes INSERT ou UPDATE : un identificateur est défini avec une valeur de non-sauvegarde qui est, par défaut, la valeur qu'il prend lorsqu'il est créé (un entier prend la valeur 0 à sa création). Vous pouvez définir [NHibernate.Mapping.Attributes.Id(UnsavedValue=?)] pour remplacer cette valeur. Donc, une nouvelle entité aura cette valeur comme identificateur et la session saura qu'elle doit insérer cette entité. Référez-vous à la documentation pour voir comment personnaliser encore plus ce comportement. Il y a toujours les méthodes session.Save() et session.Update() si vous en avez besoin.

Implémentation de la méthode Boutique.ChangeLeFuseauHoraire() :

Ajout de 'n' heures à la date de toutes les commandes

public void ChangeLeFuseauHoraire(int n)
{
NHibernate.ISession session = null;
NHibernate.ITransaction transaction = null;

System.Console.Out.WriteLine("\nChangement du fuseau horaire de toutes les commandes: n=" + n + "...");
try
{
session = _sessionFactory.OpenSession();
transaction = session.BeginTransaction();

System.Collections.IList commandes = session.CreateCriteria(typeof(Commande)).List();
// identique à: session.Find("from Commande");
foreach(Commande c in commandes)
c.ChangeLeFuseauHoraire(n);
// Il est inutile d'appeler Update(), la Session va automatiquement
// détecter les entités modifiées (tant que c'est bien elle qui les a chargés)
System.Console.Out.WriteLine(commandes.Count + " commandes modifiées!");

// Envoie les modifications (=> Génère et exécute les requêtes)
transaction.Commit();
}
catch
{
if(transaction != null)
transaction.Rollback(); // Erreur => nous DEVONT annuler les modifications
throw; // Ici, nous renvoyons la même exception pour qu'elle soit gérée (imprimée)
}
finally
{
if(session != null)
session.Close();
}
}

Méthodes pour charger toutes les commandes

session.CreateCriteria(typeof(Commande)).List();
// Equivalent à:
session.Find("from Commande");
// "select" n'est pas nécessaire lorsqu'on veut des entités (dans de simples requêtes)
//, mais vous pouvez écrire:
session.Find("select c from Commande c");

Une fois chargées, ces commandes sont gardées dans le cache de la session (premier niveau). Ce qui signifie que vous n'avez pas à les sauvegarder à nouveau. Lors de l'exécution de transaction.Commit(); la session va parcourir son cache pour détecter les entités modifiées et vas les persister. Cette fonctionnalité s'appelle la persistance transparente.

XII-D - DELETE : Détruire(HQL) et Détruire(Entité)

Il existe deux méthodes pour détruire des entités  : vous pouvez soit exécuter une requête HQL soit envoyer l'entité à détruire.

Avec le HQL, NHibernate va d'abord charger toutes les entités conformes aux critères (pour mettre ses caches à jour) et ensuite, il va les effacer (et effacer toutes les entités reliées en cascade si nécessaire). Sil vous avez déjà l'entité, vous pouvez appeler session.Delete(entité).

Implémentation de la méthode Boutique.Détruire() :

Effacement de la commande

public void Détruire(int id)
{
NHibernate.ISession session = null;
NHibernate.ITransaction transaction = null;

System.Console.Out.WriteLine("\nEffacement de la commande N° " + id + "...");
try
{
session = _sessionFactory.OpenSession();
transaction = session.BeginTransaction();

session.Delete("from Commande c where c.Id = :Id", id, NHibernate.NHibernateUtil.Int32);

// Envoie les modifications (=> Génère et exécute les requêtes)
transaction.Commit();
}
catch
{
if(transaction != null)
transaction.Rollback(); // Erreur => nous DEVONT annuler les modifications
throw; // Ici, nous renvoyons la même exception pour qu'elle soit gérée (imprimée)
}
finally
{
if(session != null)
session.Close();
}
}

La nouvelle notion ici est l'utilisation de paramètres: un objet (l'id) et le descripteur de son type (Nhibernate.NHibernateUtil.Int32).

Une autre façon d'effacer

session.Delete("from Commande c where c.Id = " + id);

Mais ce n'est pas une pratique recommandée. L'usage de paramètres empêche certaines attaques par injection SQL et reformate les caractères invalides. Et vous pouvez aussi obtenir des performances légèrement meilleures.

XIII - Conclusion

Dans ce tutoriel, nous avons: conçus une simple boutique, implémenté sa classe Commande, configuré NHibernate et implémenté les méthodes de la boutique (effectuant des opérations CRUD).

Le plus évident avantage de l'utilisation d'un MOR (pour les débutants) est la simplicité pour effectuer des opérations CRUD. Lorsque vous êtes habitué à l'API, vous pouvez charger, insérer, mettre à jour et effacer des entités sans être conscient de la base de données utilisée; vous n'avez même pas besoin de connaître le SQL. Mais ces connaissances restent importantes si vous voulez obtenir les meilleures performances.

Enfin, vous pouvez concevoir votre application avec la POO à l'esprit et ensuite, créer une base de données qui peut stocker vos entités.

Mais NHibernate est aussi impressionnant lorsqu'on a affaire à d'anciennes bases de données. Vous pouvez prendre une base de données relationnelle classique et l'associer à vos entités orientées objet.

Vous devez comprendre que si le DataSet n'est pas la bonne solution à tous les problèmes, Le MOR n'est pas non plus parfait dans toutes les situations. La génération de rapports et le traitement par lots sont des situations où le DataSet et le DataReader peuvent être plus performants. Et les utiliser demande probablement moins de réflexions dans de simples applications. Remarquez que toutes ces techniques peuvent cohabiter dans le même logiciel; utilisez le bon outil pour le bon travail.

Maintenant, je vous suggère d'essayer de réaliser une Application Console similaire à celle-ci sans regarder ce tutoriel et son code source trop souvent. Vous pouvez aussi essayer d'autres trucs. Après cela, vous comprendrez mieux et vous mémoriserez plus de détails.

Un outil que vous devriez vraiment essayer c'est  L'Analyseur de Requêtes pour NHibernate. Il sert à éditer des fichiers de configuration XML et de liaison (.hbm.xml) pour NHibernate. Vous pouvez aussi facilement exécuter des requêtes HQL après avoir défini la configuration. Pour le voir en action, téléchargez les vidéos flash disponibles sur le site web.

Si vous avez besoin de plus d'informations et d'aide
En premier lieu, lisez la documentation de NHibernate! Elle est disponible  en ligne et contient beaucoup d'articles.

Et si vous voulez discuter de ce tutoriel (et de NHibernate en général), utilisez les  forums de Developpez.com ou le  forum de NHibernate.

Vous pouvez aussi jeter un coup d'œil au  traqueur de bugs et à la  liste de publipostage de NHibernate.

Bonne chance ;)

Pierre Henri Kuaté.