Suite des essais LINQ to NHibernate

2010-02-19 #linq#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...