blog.pagesd.info

Aller au contenu | Aller au menu | Aller à la recherche

mercredi 23 juin 2010

Activer l'intellisense pour le mapping NHibernate dans Visual Studio

Pour que Visual Studio propose l'intellisence quand on édite les fichier de configuration ou de mapping de NHibernate, il faut définir la propriété "Schema" pour qu'elle pointe sur le fichier "nhibernate-mapping.xsd" ou "nhibernate-configuration.xsd".

Mais plutôt que de faire ça à la main, il est beaucoup plus simple de référencer ces deux fichiers une bon une fois pour toute au niveau de Visual Studio. Pour cela, il suffit de copier les fichiers "nhibernate-mapping.xsd" et "nhibernate-configuration.xsd" dans le répertoire destiné à contenir les schémas pour Visual Studio. Généralement (en tout cas dans mon cas), ce répertoire est : C:\Program Files (x86)\Microsoft Visual Studio 9.0\Xml\Schemas.

Et c'est tout (à moins qu'il m'ai fallu relancer Visual Studio ?).

mardi 23 mars 2010

Porter Contact Manager sous NHibernate

Après avoir fait quelques essais très basiques avec NHibernate, j'ai voulu essayer d'être plus concret et d'aller un peu plus loin en tentant de porter l'application Contact Manager sous NHibernate. A priori, ça ne devait pas être trop compliqué dans la mesure où il y a quelques temps j'avais déjà remplacé Entity Framework par LINQ to SQL et que ça ne s'était pas trop mal passé. Sans compter, que je souhaitais juste faire en sorte que ça marche à peu près tel quel, sans trop chercher à découvrir ou à mettre au point la "bonne" façon d'utiliser NHibernate avec ASP.NET MVC.

Dans la pratique, ça m'a pris un peu plus de temps que ce que j'avais pensé y consacrer. En fait, j'aurais pu faire ce billet depuis un petit moment, mais avant il fallait que j'arrive à mettre au propre la solution que j'avais suivie. Parce que c'est là que le bât blesse, c'est qu'avec NHibernate il y a tout plein de façons de faire et que je n'ai pas réussi à trouver une documentation "officielle" (et récente) qui décrive clairement quel est l'état de l'art pour utiliser NHibernate dans le cadre d'une application ASP.NET.

Voici donc parmi tous mes essais (avec des réussites, des ratés et des abandons) une des façons de faire à laquelle je suis arrivé et qui est assez "simple" à expliquer. Ca marche correctement, mais je ne peux absolument pas affirmer que c'est la méthode à suivre.

Etape 1 : Installer NHibernate

Pour faire simple, le plus pratique est d'aller sur le site de Castle ActiveRecord et d'y télécharger la toute dernière version d'Active Record. Une fois l'archive décompressée, il suffit de créer un répertoire "lib" dans le répertoire racine de "ContactManager" et d'y copier les quelques fichiers suivants :

  • Iesi.Collections.dll
  • Iesi.Collections.xml
  • LinFu.DynamicProxy.dll
  • log4net.dll
  • log4net.xml
  • nhibernate-configuration.xsd
  • nhibernate-mapping.xsd
  • NHibernate.ByteCode.LinFu.dll
  • NHibernate.ByteCode.LinFu.xml
  • NHibernate.dll
  • NHibernate.Linq.dll
  • NHibernate.xml

En réalité, je n'utilise pas du tout l'approche Active Record. Mais l'avantage de passer par leur site c'est qu'on est certain d'avoir DLLs récentes avec des versions compatibles entre elles. Au cours de mes différents essais, j'avais récupéré des DLLs au fur et à mesure de mes besoins et d'un petit peu tous les côtés et ça m'avait joué quelques mauvais tours.

Une fois ces différentes librairies mises en place, il suffit de référencer les 4 éléments suivants dans l'application ContactManager existante :

  • log4net.dll
  • NHibernate.dll
  • NHibernate.ByteCode.LinFu.dll
  • NHibernate.Linq.dll

Et pour bien faire les choses, il faut aussi prendre le temps de dé-référencer les éléments qui correspondent à Entity Framework et par conséquent de supprimer les fichiers liés à EF dans le sous-répertoire Models.

Etape 2 : Configurer NHibernate

NHibernate est installé et référencé au niveau du projet. Il faut maintenant le configurer pour lui expliquer qu'on aimerait bien qu'il utilise notre base de données SQL Server habituelle. Et là commence la galère puisqu'il existe apparement des tonnes de façon différentes pour faire ça. J'en ai essayé quelques unes et je n'ai pas vraiment très bien compris quels sont les avantages et les inconvénients de telle ou telle façon de faire (mais c'est vrai que j'ai pas trop passé de temps dessus).

Par conséquent, je vais aller au plus simple et donner directement la méthode que j'ai repérée sur la vidéo de présentation de NHibernate proposée par TekPub. C'est juste un extrait, mais ça m'a suffi pour emprunter le source à l'application Kona de Rob Conery. Cela consiste à créer un fichier "nhibernate.config" à la racine de l'application ASP.NET MVC pour y définir tous les éléments nécessaires pour configurer NHibernate :

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">

  <session-factory name="ContactManager">
    <property name="dialect">NHibernate.Dialect.MsSql2008Dialect</property>
    <property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
    <property name="connection.connection_string">server=.\SQLExpress;database=ContactManagerDB;Integrated Security=true;</property>
    <property name="show_sql">true</property>
    <property name="cache.use_query_cache">false</property>
    <property name="adonet.batch_size">16</property>
    <property name="proxyfactory.factory_class">NHibernate.ByteCode.LinFu.ProxyFactoryFactory, NHibernate.ByteCode.LinFu</property>
    <mapping assembly="ContactManager" />
  </session-factory>
  
</hibernate-configuration>

Par rapport à l'écriture de ces informations dans le Web.config, il me semble que c'est un peu plus clair d'isoler ça dans un fichier à part (sans compter que ça évite de complexitrifier le fichier Web.config inutilement). Sinon, il aurait aussi été possible d'utiliser directement le fichier "hibernate.cfg.xml" standard mais je trouve qu'un fichier ".config" c'est plus propre (et plus sûr ?) qu'un fichier ".xml".

Etape 3 : Gérer une session NHibernate

Dans la version Entity Framework de l'application Contact Manager, on utilise un objet ContactManagerDBEntities généré automatiquement par ADO.NET EF pour communiquer avec la base de données SQL Server. Dans la version portée sous LINQ to SQL, c'est un objet ContactManagerModelDataContext qui joue ce rôle.

En ce qui concerne NHibernate, les communications avec la base de données se font par l'intermédiaire d'un objet Session. Et là aussi, pour gérer / instancier cet objet Session, il existe tout plein de façons de faire.

Pour l'instant, je reprend de façon ultra simplifiée la méthode proposée par Rob Conery parce qu'il est possible de la mettre en oeuvre en très très peu de lignes au niveau du fichier global.asax.cs :

using System;
using System.IO;
using System.Web.Mvc;
using System.Web.Routing;
using NHibernate;
using NHibernate.Cfg;

namespace ContactManager
{

    public class MvcApplication : System.Web.HttpApplication
    {

        public static ISessionFactory SessionFactory = CreateSessionFactory();

        private static ISessionFactory CreateSessionFactory()
        {
            var cfg = new Configuration().Configure(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "nhibernate.config"));
            return cfg.BuildSessionFactory();
        }

Grâce à ça, il devient tout simple de remplacer la ligne :

private ContactManagerDBEntities _entities = new ContactManagerDBEntities();

par l'instruction suivante :

private ISession _session = MvcApplication.SessionFactory.GetCurrentSession();

Etape 4 : Faire le mapping entre les tables et les objets

Avec NHibernate, les classes Contact et Group ne sont pas générées automatiquement comme avec Entity Framework ou LINQ to SQL. Il faut donc créer à la main des classes POCO (c'est pas le plus compliqué) et il faut également créer un fichier de mapping XML pour associer chaque classe à la table correspondante dans la base de données (c'est déjà un peu plus mariole).

Je ne vais pas expliquer comment faire ni entrer dans les détails de comment ça marche (en plus je n'ai pas encore suffisament bien compris moi-même), mais voici ce à quoi je suis arrivé après quelques essais.

Models\Contact.cs

namespace ContactManager.Models
{
    public partial class Contact
    {
        public Contact()
        {
            Group = new Group();
        }
        public virtual int Id { get; set; }
        public virtual string FirstName { get; set; }
        public virtual string LastName { get; set; }
        public virtual string Phone { get; set; }
        public virtual string Email { get; set; }
        public virtual Group Group { get; set; }

        /// <summary>
        /// Create a new Contact object.
        /// </summary>
        /// <param name="id">Initial value of Id.</param>
        /// <param name="firstName">Initial value of FirstName.</param>
        /// <param name="lastName">Initial value of LastName.</param>
        /// <param name="phone">Initial value of Phone.</param>
        /// <param name="email">Initial value of Email.</param>
        public static Contact CreateContact(int id, string firstName, string lastName, string phone, string email)
        {
            Contact contact = new Contact();
            contact.Id = id;
            contact.FirstName = firstName;
            contact.LastName = lastName;
            contact.Phone = phone;
            contact.Email = email;
            return contact;
        }
    }

}

Mappings\Contact.hbm.xml

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="ContactManager.Models" assembly="ContactManager">

  <class name="Contact" table="Contacts" dynamic-update="true">

    <cache usage="read-write" />

    <id name="Id" column="Id" type="integer">
      <generator class="identity" />
    </id>
    <property name="FirstName" type="string" />
    <property name="LastName" type="string" />
    <property name="Phone" type="string" />
    <property name="Email" type="string" />
    <many-to-one name="Group" column="groupId" not-null="true" />

  </class>

</hibernate-mapping>

Models\Group.cs

using System.Collections.Generic;

namespace ContactManager.Models
{
    public class Group
    {
        public Group()
        {
            Contacts = new List<Contact>();
        }
        public virtual int Id { get; set; }
        public virtual string Name { get; set; }
        public virtual IList<Contact> Contacts { get; set; }
    }
}

Mappings\Group.hbm.xml

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="ContactManager.Models" assembly="ContactManager">

  <class name="Group" table="Groups" dynamic-update="true">

    <cache usage="read-write" />

    <id name="Id" column="Id" type="integer">
      <generator class="identity" />
    </id>
    <property name="Name" type="string" />
    <bag
        name="Contacts"
        inverse="true"
        lazy="true">
      <key column="groupId" />
      <one-to-many class="Contact" />
    </bag>

  </class>

</hibernate-mapping>

En ce qui concerne les deux fichiers Contact.hbm.xml et Group.hbm.xml, il faut impérativement modifier leur propriété "Action de génération" au niveau de l'explorateur de projet pour la définir à "Ressource incorporée" sans quoi le mapping NHibernate ne fonctionne pas. C'est un problème très facile à identifier parce qu'on obtient une erreur "Association references unmapped class: ContactManager.Models.Contact" dès le lancement de l'application.

Redévelopper le repository avec NHibernate

Après ces 4 premières étapes, tout est enfin prêt pour pouvoir modifier la couche repository de l'application afin de remplacer Entity Framework par NHibernate :

  • NHibernate est installé et référencé
  • NHibernate est configuré pour accéder à la base de données SQL Server
  • Un objet NHibernate Session a été défini pour communiquer avec la base de données
  • Les objets données et les fichiers de mapping NHibernate sont en place

Par conséquent, il ne reste plus qu'à implémenter l'interface IContactManagerRepository en utilisant NHibernate et LINQ to NHibernate pour remplacer le fichier EntityContactManagerRepository.cs qui l'implémentait pour Entity Framework.

Models\NHContactManagerRepository.cs

using System.Collections.Generic;
using System.Linq;
using NHibernate;
using NHibernate.Linq;

namespace ContactManager.Models
{
    public class NHContactManagerRepository : IContactManagerRepository
    {
        private ISession _session = ContactManager.MvcApplication.SessionFactory.OpenSession();

        public Contact GetContact(int id)
        {
            return (from c in _session.Linq<Contact>()
                    where c.Id == id
                    select c).FirstOrDefault();
        }

        public Contact CreateContact(int groupId, Contact contactToCreate)
        {
            // Associate group with contact
            contactToCreate.Group = GetGroup(groupId);

            // Save new contact
            _session.SaveOrUpdate(contactToCreate);
            return contactToCreate;
        }

        public Contact EditContact(int groupId, Contact contactToEdit)
        {
            // Associate with new group
            contactToEdit.Group = GetGroup(groupId);

            // Save changes
            _session.SaveOrUpdate(contactToEdit);
            _session.Flush();
            return contactToEdit;
        }

        public void DeleteContact(Contact contactToDelete)
        {
            _session.Delete(contactToDelete);
            _session.Flush();
        }

        public Group CreateGroup(Group groupToCreate)
        {
            _session.SaveOrUpdate(groupToCreate);
            return groupToCreate;
        }

        public IEnumerable<Group> ListGroups()
        {
            return _session.Linq<Group>().OrderBy(o => o.Name).ToList();
        }

        public Group GetFirstGroup()
        {
            return _session.Linq<Group>().FirstOrDefault();
        }

        public Group GetGroup(int id)
        {
            return (from g in _session.Linq<Group>()
                    where g.Id == id
                    select g).FirstOrDefault();
        }

        public void DeleteGroup(Group groupToDelete)
        {
            _session.Delete(groupToDelete);
            _session.Flush();
        }

    }
}

Une fois encore, je n'ai pas cherché à faire "bien" mais simplement à ce que ça marche. Dans la pratique, il vaudrait mieux éviter tous ces "_session.Flush()" et passer par des transactions et des "using" mais pour l'instant, ce n'était absolument pas mon objectif.

Pour pouvoir compiler et vérifier que tout fonctionne comme prévu, il reste encore à modifier le constructeur ContactManagerService pour qu'il utilise ce repository en lieu et place de celui d'Entity Framework.

public ContactManagerService(IValidationDictionary validationDictionary)
    : this(validationDictionary, new NHContactManagerRepository())
{
}

Une fois cette ultime modification réalisée, la solution compile sans aucune erreur et elle m'offre bien les mêmes fonctionnalités que les versions avec Entity Framework ou LINQ to SQL :

  • lister les contacts rattachés à un groupe,
  • ajouter, modifier ou supprimer un contact,
  • ajouter ou supprimer un groupe de contacts.

Et juste pour le plaisir, je peux lancer les quelques tests unitaires de ContactManager.Test et tout va bien !

Conclusion

Par rapport au passage sous LINQ to SQL, ça a été beaucoup plus laborieux. Le problème, c'est qu'il y a à la fois beaucoup (trop) de façon de faire et beaucoup (trop) d'informations disponibles ce qui fait qu'il n'est vraiment pas évident de savoir dans quelle direction partir.

Mais malgré tout, c'est quand même un succès. Il a suffi de changer le repository (et de recréer les classes Contact et Group générées automatiquement) pour que l'application ContactManager de base fonctionne correctement avec NHibernate. C'est donc un nouveau bon point pour le pattern Repository (zut! juste au moment où j'avais presque envie de me (ré)orienter vers Active Record). Et avec la prochaine version de Entity Framework qui devrait être POCO friendly, on peut même supposer que ce sera de plus en plus facile de passer d'un ORM à l'autre.

Peut-être que plus tard (c'est à dire une fois que je saurai faire), j'écrirai un autre billet pour présenter une façon d'utiliser Nibernate avec ASPNET MVC qui soit un peu plus orthodoxe. Mais déjà, j'aimerai très vite essayer de voir ce que ça donne en changeant la base de données et en testant cette version de Contactmanager NHibernate avec MySQL ou Oracle.

vendredi 19 février 2010

Afficher le SQL généré par NHibernate

Problème

Quand on débute avec un ORM (et avec NHibernate en l'occurrence), il n'est pas très très simple de comprendre ce qui va se passer au niveau de la base de données quand on fait tel ou tel truc avec NHibernate et encore moins quand on s'essaie à faire des requêtes LINQ to NHibernate. Pour quelqu'un qui comme moi écrit du SQL depuis des années, l'idéal ce serait donc d'arriver à voir de mes propres yeux le code SQL que fabrique NHibernate.

Au début, j'ai donc eu la réaction classique et je me suis demandé pourquoi diable je ne parvenais pas à voir le source des requêtes SQL générées par NHibernate alors que j'avais pourtant bien initialisé la propriété "show_sql" à "true" :

<property name="show_sql">true</property>

Puis j'ai finalement remarqué que ça c'était bon pour quand je faisais des tests unitaires ou de petits essais sous forme d'applications consoles, mais que ça n'était pas prévu pour les applications web (et même pas du tout puisqu'il n'y avait rien).

Qu'à cela ne tienne, la réponse est dans Google. Mais là, le problème c'est qu'il y a beaucoup de réponses. Trop même. Et la plupart du temps il y a surtout tout un tas d'explications sur le fonctionnement de NHibernate et de log4net, la façon dont NHibernate utilise log4net, les différentes manipulations et configurations à faire pour qu'ils travaillent ensemble dans de bonnes conditions, comment sélectionner ce qu'on veut loguer...

Enfin beaucoup trop de trucs à assimiler quand comme moi on veut juste savoir à quoi ressemble le code SQL généré par NHibernate et qu'on n'a pas le temps et les moyens de s'intéresser aux coulisses.

Mais comme je suis persistant persévérant, j'ai quand même réussi à trouver la une solution et même qu'après un ou deux essais j'ai réussi à isoler ce qu'il était nécessaire et indispensable de faire pour que cela fonctionne de façon satisfaisante :

  • il faut bien utiliser log4net
  • il n'y a pas énormément de trucs à faire

Solution

La première phase pour utiliser log4net nécessite de le configurer au niveau du fichier Web.config. Il faut commencer par ajouter la ligne suivante à l'intérieur de la section <configSections> afin de déclarer le nom de la section de configuration qui sera dédiée à log4net :

<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />

Ensuite, il faut ajouter cette section <log4net> (juste après la section <configSections>) dans le Web.config pour configurer réellement log4net. Comme pour l'instant mon seul objectif est de pouvoir afficher les requêtes SQL au fur et à mesure que NHibernate les génère, je peux me contenter d'insérer les quelques lignes suivantes :

  <log4net>

    <appender name="TraceAppender" type="log4net.Appender.TraceAppender">
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%message%newline" />
      </layout>
    </appender>

    <logger name="NHibernate.SQL" additivity="false">
      <level value="ALL"/>
      <appender-ref ref="TraceAppender" />
    </logger>

  </log4net>

En gros, ça sert à dire que NHibernate doit loguer les requêtes SQL qu'il génère et que log4net doit afficher ces logs dans la fenêtre de débugage de Visual Studio (c'est peut être pas exactement ça, mais le résultat sera bientôt là).

Une fois cette première phase de configuration terminée, il reste à activer log4net. Pour cela, il faut tout d'abord ajouter la librairie log4net.dll dans les références du projet. Ceci fait, il reste à "démarrer" log4net, ou plus exactement à lui indiquer qu'il doit se paramétrer en utilisant la configuration définie dans le fichier Web.config.

Pour ça, il suffit d'ajouter une ligne log4net.Config.XmlConfigurator.Configure(); au niveau de la procédure Application_Start dans le Global.axax.cs (et peut-être un using log4net; en début de fichier) :

using log4net;

...
        protected void Application_Start()
        {
            log4net.Config.XmlConfigurator.Configure();
            RegisterRoutes(RouteTable.Routes);
        }

Et c'est tout ! En fait, on n'a même pas besoin d'avoir initialisé la propriété "show_sql" à "true" pour que ça marche :

Nhibernate_Log4net.png

Pour aller plus loin

Trois liens intéressants parmi tant d'autres qui m'ont permis d'arriver à cette solution :

jeudi 18 février 2010

Suite des essais LINQ to NHibernate

Cette fois-ci je vais me contenter de refaire le même genre de tests que lors de mes premiers essais avec LINQ to NHibernate d'hier. Mais aujourd'hui, au lieu de partir d'un contact pour accéder au groupe auquel il est lié, je vais explorer l'autre côté de la relation entre les deux tables et partir d'un groupe puis accéder aux contacts qui lui sont rattachés.

Essai numéro 1

Je commence doucement avec une requête LINQ toute simple :

var linq1 = (from g in _session.Linq<Group>()
             where g.Id == id
             select g).FirstOrDefault();

Sans trop de surprise (maintenant que j'ai quelques essais au compteur), NHibernate génère la requête SQL suivante :

SELECT top 1
       this_.Id as Id0_0_, 
       this_.Name as Name0_0_ 
FROM   Groups this_ 
WHERE  this_.Id = @p0; // @p0 = 1

Puis j'essaie d'accéder à la propriété Contacts de l'objet Group que vient de renvoyer la première requête LINQ :

int count = linq1.Contacts.Count();

NHibernate génère alors une seconde requête SQL afin de charger la liste des contacts associés au groupe :

SELECT contacts0_.groupId as groupId1_, 
       contacts0_.Id as Id1_, 
       contacts0_.Id as Id1_0_, 
       contacts0_.FirstName as FirstName1_0_, 
       contacts0_.LastName as LastName1_0_, 
       contacts0_.Phone as Phone1_0_, 
       contacts0_.Email as Email1_0_, 
       contacts0_.groupId as groupId1_0_ 
FROM   Contacts contacts0_ 
WHERE  contacts0_.groupId=@p0; // @p0 = 1

Jusqu'ici, c'est tout pareil par rapport aux résultats que que j'avais obtenus au cours de mon premier essai dans l'autre sens.

Essai numéro 2

Comme lors de mes premiers tests, ce deuxième essai va consister à utiliser la méthode Extend("Entité") pour vérifier que cela fonctionne à peu près de la même façon que pour aller du contact vers le groupe. Ce qui me donne donc la requête LINQ suivante :

var linq2 = (from g in _session.Linq<Group>().Expand("Contacts")
             where g.Id == id
             select g).FirstOrDefault();

Et comme je pouvais presque m'y attendre, cela a eu pour effet de faire apparaitre une jointure avec la table Contacts dans la requête SQL qui est générée par NHibernate :

SELECT top 1 
       this_.Id as Id0_1_, 
       this_.Name as Name0_1_, 
       contacts2_.groupId as groupId3_, 
       contacts2_.Id as Id3_, 
       contacts2_.Id as Id1_0_, 
       contacts2_.FirstName as FirstName1_0_, 
       contacts2_.LastName as LastName1_0_, 
       contacts2_.Phone as Phone1_0_, 
       contacts2_.Email as Email1_0_, 
       contacts2_.groupId as groupId1_0_ 
FROM   Groups this_ 
       left outer join Contacts contacts2_ on this_.Id=contacts2_.groupId
WHERE  this_.Id = @p0; // @p0 = 1

Et comme hier, le fait d'accéder à la liste des contacts de l'objet Group renvoyé n'a pas d'impact et NHibernate ne génère pas une nouvelle requête SQL :

int count2 = linq2.Contacts.Count();

Jusqu'ici, tout va toujours bien puisque NHibernate se comporte toujours rigoureusement de la même façon dans les deux sens de la relation.

Interlude

Pour rester le plus synchro possible avec mon billet précédent, voici le fichier de mapping NHibernate que j'ai défini pour les groupes :

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="ContactManager.Models" assembly="ContactManager">

  <class name="Group" table="Groups" dynamic-update="true">

    <cache usage="read-write" />

    <id name="Id" column="Id" type="integer">
      <generator class="identity" />
    </id>
    <property name="Name" type="string" />
    <bag
        name="Contacts"
        inverse="true"
        lazy="true">
      <key column="groupId" />
      <one-to-many class="Contact" />
    </bag>

  </class>

 </hibernate-mapping>

Comme dans le fichier Contact.hbm.xml d'hier, ce fichier de mapping spécifie également la relation qui existe entre les tables Groups et Contacts. Par rapport à la simple balise <many-to-one ... /> d'hier, il faut utiliser un élément <bag ... /> pour représenter le fait qu'à un groupe sont liés plusieurs contacts.

Essai numéro 3

Dans le fichier de mapping Group.hbm.xml, le fait que je fasse du lazy-loading est pour l'instant défini de façon explicite grâce à l'attribut lazy="false". Par conséquent, si je souhaite maintenant ne plus faire de lazy-loading, il est donc nécessaire que je modifie le mapping NHibernate de la façon suivante :

...
    <bag
        name="Contacts"
        inverse="true"
        lazy="false">
      <key column="groupId" />
      <one-to-many class="Contact" />
    </bag>
...

Et désormais, si je reprend la même requête LINQ qu'au début de ce billet :

var linq3 = (from g in _session.Linq<Group>()
             where g.Id == id
             select g).FirstOrDefault();

Lorsque je l'exécute, je peux voir que NHibernate a cette fois-ci généré 2 requêtes SQL différentes. La première sert pour charger les informations du groupe et la seconde permet de charger les données correspondantes aux contacts qui sont rattachés au groupe :

SELECT top 1 
       this_.Id as Id0_0_, 
       this_.Name as Name0_0_ 
FROM   Groups this_ 
WHERE  this_.Id = @p0; // @p0 = 1

SELECT contacts0_.groupId as groupId1_, 
       contacts0_.Id as Id1_, 
       contacts0_.Id as Id1_0_, 
       contacts0_.FirstName as FirstName1_0_, 
       contacts0_.LastName as LastName1_0_, 
       contacts0_.Phone as Phone1_0_, 
       contacts0_.Email as Email1_0_, 
       contacts0_.groupId as groupId1_0_ 
FROM   Contacts contacts0_ 
WHERE  contacts0_.groupId=@p0; // @p0 = 1

Deuxième bilan

Ce qui est vraiment bien, c'est que ça fonctionne exactement de la même façon dans les deux sens ! Et donc normalement, une fois que je serai un peu mieux rodé à l'utilisation de NHibernate et de LINQ to NHibernate, je devrais grosso-modo réussir à prévoir ce qui va se passer sans trop de problème.

Par contre, j'ai encore pas mal de difficultés en ce qui concerne le mapping. Pour l'instant je fais surtout du copié / collé de différents trucs trouvés sur internet (dont le tutoriel Utiliser ASP.NET MVC et NHibernate) => je ne sais pas trop (et je me demande) s'il est nécessaire / obligatoire / conseillé de définir la relation dans les deux fichiers de mapping ou si on peut se contenter de faire ça d'un seul côté (c'est à dire dans un seul fichier de mapping). C'est d'ailleurs peut-être à ça que sert l'attribut inverse="true" ? => Encore pas mal trucs à creuser pour plus tard...

mercredi 17 février 2010

Premiers essais avec LINQ to NHibernate

Etant donné que je débute avec NHibernate et LINQ to NHibernate, j'ai fait quelques essais pour comprendre ce qui se passe côté SQL quand je fais telle ou telle requête LINQ.

Essai numéro 1

Quand je fais la requête LINQ suivante :

var linq1 = (from c in _session.Linq<Contact>()
            where c.Id == id
            select c).FirstOrDefault();

NHibernate génère la requête SQL ci-dessous :

SELECT top 1
       this_.Id as Id1_0_, 
       this_.FirstName as FirstName1_0_, 
       this_.LastName as LastName1_0_, 
       this_.Phone as Phone1_0_, 
       this_.Email as Email1_0_, 
       this_.groupId as groupId1_0_ 
FROM   Contacts this_ 
WHERE  this_.Id = @p0; // @p0 = 13

Ensuite, lorsque j'accède à une propriété liée de l'objet Contact qui a été retourné par la requête LINQ :

string NomGroupe = linq1.Group.Name;

Alors NHibernate va générer une seconde requête SQL pour charger l'objet Group associé au contact :

SELECT group0_.Id as Id0_0_, 
       group0_.Name as Name0_0_ 
FROM   Groups group0_ 
WHERE  group0_.Id=@p0; // @p0 = 1

Essai numéro 2

Après quelques recherches, j'ai trouvé qu'il était possible de reproduire la méthode Include("Entité") de ADO.NET Entity Framework en utilisant la méthode Expand(). Cela me permet donc d'écrire ma requête LINQ de la façon suivante :

var linq2 = (from c in _session.Linq<Contact>().Expand("Group")
            where c.Id == id
            select c).FirstOrDefault();

Et dans ce cas, NHibernate insère automatiquement une jointure avec la table Groups dans la requête SQL qu'il génère :

SELECT top 1 
       this_.Id as Id1_1_, 
       this_.FirstName as FirstName1_1_, 
       this_.LastName as LastName1_1_, 
       this_.Phone as Phone1_1_, 
       this_.Email as Email1_1_, 
       this_.groupId as groupId1_1_, 
       group2_.Id as Id0_0_, 
       group2_.Name as Name0_0_ 
FROM   Contacts this_ 
       inner join Groups group2_ on this_.groupId=group2_.Id
WHERE  this_.Id = @p0; // @p0 = 13

Comme je suis d'un naturel méfiant, je suis allé jusqu'à vérifier ce qui se passait quand j'accédais à la propriété Group de l'objet Contact :

string NomGroupe2 = linq2.Group.Name;

Et fort heureusement il ne se passe rien et NHibernate ne génère pas de requête SQL supplémentaire.

Interlude

OK. Mais NHibernate ça ne peut pas être aussi simple que ça et il y des tas de trucs à prendre en compte. Jusqu'à maintenant, j'ai fait mes deux essais avec le fichier de mapping suivant pour les contacts :

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="ContactManager.Models" assembly="ContactManager">

  <class name="Contact" table="Contacts" dynamic-update="true">

    <cache usage="read-write" />

    <id name="Id" column="Id" type="integer">
      <generator class="identity" />
    </id>
    <property name="FirstName" type="string" />
    <property name="LastName" type="string" />
    <property name="Phone" type="string" />
    <property name="Email" type="string" />
    <many-to-one name="Group" column="groupId" not-null="true" />

  </class>

</hibernate-mapping>

Dans ce fichier, il y a une ligne <many-to-one name="Group" column="groupId" not-null="true" /> qui sert à définir la relation entre les tables Contacts et Groups :

  • 1 contact appartient à 1 groupe
  • 1 groupe peut contenir 0 à N contact(s)

Par défaut, cette relation "fonctionne" en mode lazy-loading, ce qui signifie que le groupe ne sera chargé que lorsqu'on en aura réellement besoin. Et au vu de mes deux premiers essais, je ne peux qu'être satisfait puisque c'est exactement comme cela que ça s'est passé.

Essai numéro 3

Et maintenant, supposons que je ne veuille plus faire de lazy-loading ? Pour commencer, je dois ajouter lazy="false" à mon fichier de mapping :

...
    <many-to-one name="Group" column="groupId" not-null="true" lazy="false" />
...

Et ce coup-ci, quand je refais la même requête LINQ que lors de mon premier essai :

var linq3 = (from c in _session.Linq<Contact>()
            where c.Id == id
            select c).FirstOrDefault();

Je peux constater que NHibernate a généré directement les 2 requêtes SQL suivantes :

SELECT top 1
       this_.Id as Id1_0_, 
       this_.FirstName as FirstName1_0_, 
       this_.LastName as LastName1_0_, 
       this_.Phone as Phone1_0_, 
       this_.Email as Email1_0_, 
       this_.groupId as groupId1_0_ 
FROM   Contacts this_ 
WHERE  this_.Id = @p0; // @p0 = 13

SELECT group0_.Id as Id0_0_, 
       group0_.Name as Name0_0_ 
FROM   Groups group0_ 
WHERE  group0_.Id=@p0; // @p0 = 1

Par acquit de conscience j'ai re-vérifié mon premier essai sans lazy="false". Pas de problème : NHibernate ne génère bien que la 1° requête SQL si je me contente de faire la requête LINQ et que je ne cherche pas à accéder à l'objet Group.

Premier bilan

Au moins, ça commence à être un peu plus clair pour ce côté de la relation. Si tout va bien, je ferais plus tard quelques essais supplémentaires pour étudier comment ça se passe dans le cas où je pars d'un groupe et que j'accède ensuite à ses contacts.

J'ai pas trainé : la suite de mes essais avec LINQ to NHibernate !

vendredi 29 janvier 2010

Utiliser ASP.NET MVC et NHibernate (3° partie)

Ceci est la traduction du billet "Using ASP.NET MVC and NHibernate (Part 3)" de César Intriago.

Voici le troisième et dernier billet d'une série d'articles consacré à l'utilisation de NHibernate pour développer des applications ASP.NET MVC. Les liens vers les deux premières parties de ce tutoriel sont disponibles ci-dessous :

Dans cet article, nous allons créer notre application ASP.NET MVC pour gérer les Posts et les Categories. Notre projet aura la structure suivante :

Le contrôleur CategoriesController va nous permettre de retrouver, créer, modifier et supprimer des catégories de notre repository.

Le contrôleur PostController va nous permettre de faire la même chose pour les Posts. En ce qui concerne les vues pour les Posts, je vais utiliser une ViewModel afin de représenter la combinaison des Posts et des Categories.

Dans notre projet ASP.NET MVC nous allons ajouter des références vers nos projets Core et Infrastructure afin de pouvoir utiliser les composants NHibernate pour la persistance que nous avons développé dans les deux premières parties.

Nous utiliserons des vues fortement typées basées sur notre modèle et ViewModel afin que Visual Studio puisse scaffolder les vues en s'appuyant sur ces types d'objet.

J'ai apporté quelques modification au code par rapport à la deuxième partie car j'ai détecté quelques bugs grâce aux test unitaires. Par conséquent, il est nécessaire de télécharger la version complète du projet.

ViewModel

Les vues pour les Posts ont besoin des objets Post et Category pour fonctionner correctement. Pour cela, un pattern très répandu est de passer par un modèle de vue qui sert de conteneur pour ces deux objets afin de faciliter leur utilisation au niveau de la vue.

Par conséquent, dans le cas des vues destinées aux Posts, je n'ai pas utilisé directement les objets du modèle (c'est à dire les classes Post et Category). A la place, j'ai créé une classe PostViewModel qui contient ces deux objets du modèle. De cette façon, je peux simplifier la manipulation des Posts et des Categories auxquelles ils sont rattachés comme s'il s'agissait d'une seule entité.

Conclusion

Configurer NHibernate pour la première fois peut demander un petit effort (ou un effort certain), mais il vous fera gagner énormément de temps pour réaliser la couche données de votre application. Et combiné à un framework tel que ASP.NET MVC, vous parvenez à une franche séparation des préoccupations (separation of concerns en anglais) qui vous aidera à conserver un projet bien organisé, facile à comprendre et à tester. Je suis persuadé qu'en suivant cette méthode vous obtiendrez un projet avec un code et une structure tirés à quatre épingle, d'autant plus que les modèles de Visual Studio pour ASP.NET MVC rendent ce travail encore plus facile étant donné que vous pouvez automatiser la création des vues.

C'est extraordinaire tellement c'est facile et rapide de créer une application avec ASP.NET MVC. J'attends avec impatience la sortie de la version 2.0 (en fait je teste d'ores et déjà sa Preview). C'est vraiment le moment idéal pour apprendre ASP.NET MVC et NHibernate.

Ceci est la traduction du billet "Using ASP.NET MVC and NHibernate (Part 3)" de César Intriago.

Utiliser ASP.NET MVC et NHibernate (2° partie)

Ceci est la traduction du billet "Using ASP.NET MVC and NHibernate (Part 2)" de César Intriago.

Et voici la deuxième partie d'une série d'article consacré à l'utilisation de NHibernate dans une application ASP.NET MVC. Vous pouvez lire la première partie de ce tutoriel ici.

Représenter une relation many-to-many

Nous allons continuer en créant un fichier de mapping pour la classe Post. Celui-ci est légèrement différent de celui pour la classe Category étant donné que nous devons représenter une relation plusieurs-à-plusieurs entre les tables Posts et Categories, ce que nous réaliserons en utilisant un élément BAG dans le fichier de mapping. Voici le code de notre fichier Post.hbm.xml :

IMPORTANT : Vous devez définir l'action de génération de tous les fichiers de mapping à "Ressource incorporée" afin que NHibernate puisse trouver le bon fichier dans l'assembly.

Post.hbm.xml

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
                                    namespace="Core.Domain.Model"
                                    assembly="Core">

  <class name="Post" table="Posts" dynamic-update="true">
    <cache usage="read-write"/>
    <id name="Id" column="Id" type="Guid">
      <generator class="guid"/>
    </id>
    <property name="Title" length="100"/>
    <property name="Body"/>
    <property name="CreationDate" type="datetime"/>
    <property name="IsPublic" type="bool"/>

    <bag name="Categories" table="PostCategory" lazy="false" >
      <key column="idPost" ></key>
      <many-to-many class="Category" column="idCategory" ></many-to-many>
    </bag>

  </class>
</hibernate-mapping>

Voici quelques explications sur ce que nous avons fait au niveau de l'élément <bag> :

  • L'attribut name défini le nom de la propriété de la classe Post où nous stockerons la collection des catégories.
  • L'attribut table correspond au nom de la table dans la base de données qui relie les tables Posts et Categories.
  • L'attribut key.column est le nom de l'identifiant de la table Posts.
  • L'attribut class représente le nom de la classe Category dans le modèle d'objet.
  • L'attribut many-to-many.column est le nom de l'identifiant de la table Categories.

Pour vous aider à mieux comprendre ce fichier de mapping, voici tout d'abord à quoi ressemble le diagramme de classe :

puis le schéma de la base de données correspondante :

Comme vous pouvez le constater dans le diagramme de classe, nous n'avons pas besoin de créer une classe pour la table PostCategory.

Test unitaire

Nous allons maintenant tester nos repositorys et vérifier que nous pouvons créer des billets à l'aide des tests unitaires suivants :

using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Core.Domain.Model;
using Core.Domain.Repositories;
using Core;

namespace NHibernate101.Tests
{
    [TestClass]
    public class RepositoriesTest
    {
        IRepository<Category> categoriesRepository;
        IRepository<Post> postsRepository;
        Post testPost;
        Category testCategory1;
        Category testCategory2;

        public RepositoriesTest()
        {
        }

        private TestContext testContextInstance;

        public TestContext TestContext
        {
            get
            {
                return testContextInstance;
            }
            set
            {
                testContextInstance = value;
            }
        }

        [TestInitialize()]
        public void CreateRepositories()
        {
            categoriesRepository = new CategoryRepository();
            postsRepository = new PostRepository();
        }

        [TestMethod]
        [DeploymentItem("hibernate.cfg.xml")]
        public void CanCreateCategory()
        {
            testCategory1 = new Category() { Name = "ASP.NET" };
            categoriesRepository.Save(testCategory1);
        }

        [TestMethod]
        [DeploymentItem("hibernate.cfg.xml")]
        public void CanCreatePost()
        {
            testPost = new Post();
            testPost.Title = "ASP.NET MVC and NHibernate";
            testPost.Body = "In this article I’m going to cover how to install and configure NHibernate and use it in a ASP.NET MVC application.";
            testPost.CreationDate = DateTime.Now;
            testPost.IsPublic = true;

            testCategory2 = new Category() { Name= "ASP.NET MVC"};

            categoriesRepository.Save(testCategory2);
            testPost.Categories.Add(testCategory2);

            postsRepository.Save(testPost);
        }
    }
}

Lancer le test et s'il réussi, nous devrions retrouver le nouveau billet et la nouvelle catégorie dans la base de données :

Il s'agit là des rudiments de NHibernate. Je vous encourage à vous documenter plus en détail sur le site de la communauté NHibernate. Dans la prochaine partie de ce tutoriel nous commencerons à développer notre application ASP.NET MVC pour gérer les billets et les catégories !

Ceci est la traduction du billet "Using ASP.NET MVC and NHibernate (Part 2)" de César Intriago.

Utiliser ASP.NET MVC et NHibernate (1° partie)

Ceci est la traduction du billet "Using ASP.NET MVC and NHibernate (Part 1)" de César Intriago.

Dans cet article je vais expliquer comment installer et configurer NHibernate pour l'utiliser dans une application ASP.NET MVC. Ceci est le premier article d'une série dans laquelle je montrerai comment installer NHibernate pour une application .Net.

Utiliser ASP.NET MVC et NHibernate

NHibernate c'est quoi ?

NHibernate est un outil de mapping Objet-Relationnel (object-relational mapping ou ORM en anglais) pour .Net qui permet de faire correspondre des modèles orientés objets avec une base de données. Comme vous le verrez dans cet article, NHibernate s'occupera de la plupart des tâches relatives à la couche de persistance. Vous pouvez avoir plus d'informations au sujet de NHibernate à partir du site consacré à la communauté NHibernate.

Vous pouvez télécharger la dernière version de NHibernate sur SourceForge. Les exemples de code dans cet article sont basés sur NHibernate 2.1.1 (la version la plus récente disponible au moment de la rédaction de cet article).

Installer NHibernate

Téléchargez et dézippez NHibernate sur votre ordinateur. Ca y est, NHibernate est installé.

Créer le projet ASP.NET MVC

Créez un nouveau projet ASP.NET MVC et n'oubliez pas de créer un projet de test.

Ajoutez deux nouveaux projets de type "Bibliothèque de classe" à votre solution : Infrastructure et Core (pour que tout soit structuré du mieux possible).

Dans le projet Core, ajoutez une référence à l'assemblie NHibernate.

La base de données

Pour notre exemple, nous allons créer un modèle contenant des Posts (des billets en français) et des Categories. C'est un modèle très simple qui nous aidera à bien comprendre comment NHibernate fonctionne : un billet (ou post) peut appartenir à une ou plusieurs catégories et une catégorie peut contenir zéro ou plus de zéro billets :

Nous allons utiliser SQL Server 2008 Express Edition pour créer notre base de données, une autre solution étant de créer notre modèle dans Visual Studio et de demander à NHibernate de créer la base de données pour nous. Mais dans notre cas, nous allons créer manuellement la base de données.

Créer le Modèle

L'étape suivante va consister à créer notre modèle qui est une représentation orientée objet de notre base de données. Pour cela nous allons utiliser le concepteur de classes de Visual Studio :

Les Repository

Un Repository nous permet de créer, sélectionner, modifier et supprimer nos objets et il est indépendant de la base de données. Pour ce tutoriel, nous avons besoin de créer deux repositorys : PostRepository et CategoryRepository. Tout deux vont implémenter la même interface présentée ci-dessous :

IRepository.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace Core
{
    public interface IRepository<T>
    {
        void Save(T entity);
        void Update(T entity);
        void Delete(Guid id);
        T GetById(Guid id);
        T GetAll();
    }
}

Pour créer nous deux repositorys, nous avons d'abord besoin d'une une classe helper pour créer une session NHibernate sur notre base de données :

NHibernateHelper.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NHibernate.Cfg;
using NHibernate;

namespace Core.Domain.Repositories
{

    public class NHibernateHelper
    {
        private static ISessionFactory _sessionFactory;

        private static ISessionFactory SessionFactory
        {
            get
            {
                if (_sessionFactory == null)
                {
                    var configuration = new Configuration();
                    configuration.Configure();
                    _sessionFactory = configuration.BuildSessionFactory();
                }
                return _sessionFactory;
            }
        }

        public static ISession OpenSession()
        {
            return SessionFactory.OpenSession();
        }
    }
}

Ensuite, nous pouvons créer les repositorys :

PostRepository.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Core.Domain.Model;
using NHibernate;
using NHibernate.Criterion;
 
namespace Core.Domain.Repositories
{
    public class PostRepository: IRepository<Post>
    {
        #region IRepository<Post> Members
 
        void IRepository<Post>.Save(Post entity)
        {
            using (ISession session = NHibernateHelper.OpenSession())
            {
                using (ITransaction transaction = session.BeginTransaction())
                {
                    session.Save(entity);
                    transaction.Commit();
                }
            }
        }

        void IRepository<Post>.Update(Post entity)
        {
            using (ISession session = NHibernateHelper.OpenSession())
            {
                using (ITransaction transaction = session.BeginTransaction())
                {
                    session.Update(entity);
                    transaction.Commit();
                }
            }
        }

        void IRepository<Post>.Delete(Guid id)
        {
            using (ISession session = NHibernateHelper.OpenSession())
            {
                using (ITransaction transaction = session.BeginTransaction())
                {
                    session.Delete(id);
                    transaction.Commit();
                }
            }
        }

        Post IRepository<Post>.GetById(Guid id)
        {
            using (ISession session = NHibernateHelper.OpenSession())
                return session.CreateCriteria<Post>().Add(Restrictions.Eq("Id", id)).UniqueResult<Post>();
        }

        Post IRepository<Post>.GetAll()
        {
            throw new NotImplementedException();
        }

        #endregion
    }
}

CategoryRepository.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Core.Domain.Model;
using NHibernate;
using NHibernate.Criterion;

namespace Core.Domain.Repositories
{
    public class CategoryRepository: IRepository<Category>
    {
        #region IRepository<Category> Members

        void IRepository<Category>.Save(Category entity)
        {
            using (ISession session = NHibernateHelper.OpenSession())
            {
                using (ITransaction transaction = session.BeginTransaction())
                {
                    session.Save(entity);
                    transaction.Commit();
                }
            }
        }
 
        void IRepository<Category>.Update(Category entity)
        {
            using (ISession session = NHibernateHelper.OpenSession())
            {
                using (ITransaction transaction = session.BeginTransaction())
                {
                    session.Update(entity);
                    transaction.Commit();
                }
            }
        }

        void IRepository<Category>.Delete(Guid id)
        {
            using (ISession session = NHibernateHelper.OpenSession())
            {
                using (ITransaction transaction = session.BeginTransaction())
                {
                    session.Delete(id);
                    transaction.Commit();
                }
            }
        }

        Category IRepository<Category>.GetById(Guid id)
        {
            using (ISession session = NHibernateHelper.OpenSession())
                return session.CreateCriteria<Category>().Add(Restrictions.Eq("Id", id)).UniqueResult<Category>();
        }

        Category IRepository<Category>.GetAll()
        {
            throw new NotImplementedException();
        }

        #endregion
    }
}

Comme vous pouvez le constater, c'est dans les repositorys que nous plaçons le code pour appeler les méthodes NHibernate, ce que nous faisons en créant un objet Session en premier.

Où est-ce que nous en sommes...

Avant de continuer, revoyons un peu ce que nous avons déjà fait :

  1. Nous avons créé un projet Core qui référence la librairie NHibernadte.dll
  2. Dans ce projet Core, nous avons créé deux classes qui représentent notre modèle : "Post.cs" et "Category.cs", la classe Post ayant une collection de catégories.
  3. Nous avons cré deux repositorys pour enregistrer, modifier, supprimer et retrouver les objets de notre modèle.

Les Mappings

Il est temps de faire quelque chose dans notre projet Infrastructure où nous mapperont notre modèle à notre base de données, ce qui se fait via des fichiers XML avec NHibernate. Nous allons suivre la convention de nommage qui existe au sujet des fichiers maps : [ClassName].hbm.xml.

Nous allons créer deux nouveaux fichiers : Category.hbm.xml et Post.hbm.xml. Le contenu de chacun de ces fichiers fait correspondre une classe à une table, une propriété à une colonne, et indique aussi le type de données. Pour cette première partie du tutoriel, nous avons besoin de créer le fichier Category.hbm.xml :

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
                                    namespace="Core.Domain.Model"
                                    assembly="Core">

  <class name="Category" table="Categories" dynamic-update="true">
    <cache usage="read-write"/>
    <id name="Id" column="Id" type="Guid">
      <generator class="guid"/>
    </id>
    <property name="Name" length="100"/>
  </class>
</hibernate-mapping>

IMPORTANT : Vous devez définir l'action de génération de ces deux fichiers à "Ressource incorporée" afin que NHibernate puisse trouver le bon fichier dans l'assembly.

Configurer NHibernate

Nous sommes presque prêts. La prochaine étape va consister définir la chaine de connexion à la base de données et quelques paramètres pour NHibernate. Cette configuration doit être stockée dans un fichier "hibernate.cfg.xml" (définissez l'action de génération de ce fichier à "Ressource incorporée"). NHibernate dispose d'une fonctionnalité spéciale de "Lazy-Loading" pour dynamic proxy systems, et nous devons ajouter les références suivantes à notre projet Infrastructure pour qu'il puisse l'utiliser :

  • Castle.Core
  • Castle.DynamicProxy2
  • NHibernate.ByteCode.Castle.dll

Nous pouvons alors créer le fichier de configuration XML suivant :

hibernate.cfg.xml

<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
  <session-factory>
    <property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
    <property name="connection.connection_string">server=.\SQLExpress;database=NHibernate101;Integrated Security=true;</property>
    <property name="show_sql">true</property>
    <property name="dialect">NHibernate.Dialect.MsSql2008Dialect</property>
    <property name="cache.use_query_cache">false</property>
    <property name="adonet.batch_size">100</property>
    <property name="proxyfactory.factory_class">NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle</property>
    <mapping assembly="Infrastructure" />
  </session-factory>
</hibernate-configuration>

La ligne <property name="proxyfactory.factory_class">... utilise la fonctionalité de "lazy loading" au sujet de laquelle vous trouverez plus d'informations sur le site d'Hibernate.

Test unitaire de NHibernate

Il est enfin temps de tester notre configuration de NHibernate ! Nous allons ajouter quelques données à notre table "Categories". Pour cela nous avons besoin que notre projet de test référence l'assembly NHibernate et d'y copier le fichier hibernate.cfg.xml.

Ajouter les références suivantes :

  • Castle.Core
  • Castle.DynamicProxy2
  • Infrastructure
  • NHibernate
  • NHibernate.ByteCode.Castle

Puis ajouter la méthode de test suivante :

[TestMethod]
        [DeploymentItem("hibernate.cfg.xml")]
        public void CanCreateCategory()
        {
            IRepository<Category> repo = new CategoryRepository();
            Category category = new Category();
            category.Name = "ASP.NET";

            repo.Save(category);

        }

Lancer le projet de test et notre méthode de test CanCreateCategory devrait réussir le test (avec un peu de chance) :

Nous pouvons alors vérifier dans la base de données que la nouvelle catégorie a bien été ajoutée :

Dans la prochaine partie de ce tutoriel, nous complèterons le modèle et les tests unitaires puis nous attaquerons la création de notre application ASP.NET MVC.

Ceci est la traduction du billet "Using ASP.NET MVC and NHibernate (Part 1)" de César Intriago.