NHibernate et les propriétés magiques

Publié le 14 mars 2010 par Olivier Duval

Préambule

La différence entre la théorie et la pratique ? en théorie, il n'y en a pas, en pratique, si.

Le monde du développement est loin d'être un monde idéal, dans la réalité, nous rencontrons souvent des situations particulières auxquelles il faut faire face. Un framework, en plus de répondre aux besoins les plus généraux, se doit également de fournir des solutions de contournements, des "astuces de programmation" diront certains.

Dans ce court billet, nous allons parler de 2 fonctionnalités de NHibernate sur les propriétés lorsque notre modèle ou le contexte ne répond pas exactement à l'idéal que l'on devrait avoir en termes d'architecture.

Imaginons que l'on ait une entité Individu, que l'on affichera sur une page, sous forme d'une liste d'individus. Dans notre modèle nous avons la relation simple suivante entre un individu et son appartenance à un établissement :

La liste paginée est triable (cf. ce billet pour la pagination et le tri en NHibernate) par le nom, prénom, le nom de l'établissement. On pourrait facilement créer des entités Individu et Etablissement avec cette relation, mais l'on ne souhaite pas le faire, car nous avons uniquement besoin du nom de l'établissement pour le tri, sans vouloir représenter tout le modèle derrière Etablissement. Le tri et la pagination ne peuvent s'effectuer que par l'intermédiaire de NHibernate, nous avons donc besoin d'une proprété mappée dans une classe pour que l'ORM puisse l'atteindre et actionner le tri.

Formula

les formula permettent ce genre d'astuces de programmation : avoir une propriété EtabNom qui pointe vers une table externe (et non vers une classe comme on le ferait habituellement avec du many-to-one) mais sous forme d'une requête SQL, la propriété se tranforme alors en :

<property 
  name="EtabNom"
  formula="(select etab.NOMALPHA from ETABLISSEMENT etab where etab.ID=ETAB_ID)">
</property>

ce qui donne pour l'entité Individu, où ETAB_ID est un champ de la table INDIVIDU mappée :

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" auto-import="true" >
  <class name="MyNamespace.Individu, MyAssembly" table="INDIVIDU">
    <id name="ID" column="ID" type="System.Int32" access="property">
      <generator class="identity"></generator>
    </id>
    <property name="Nom" column="nom" not-null="true" type="System.String" access="property"/>
    <property name="Prenom" column="prenom" not-null="true" type="System.String" access="property"/>
    <property name="Email" column="email" not-null="true" type="System.String" access="property"/>
    <property name="EtabNom"
              formula="(select etab.NOMALPHA from ETABLISSEMENT etab where etab.ID=ETAB_ID)"></property>
  </class>
</hibernate-mapping>

on pourra alors trier sur la propriété individu.EtabNom sans utiliser d'entité Etablissement. Une requête Linq NHibernate du type

lasession.Linq<Individu>().OrderBy(ind => ind.EtabNom).Take(20);

se traduira en SQL de la façon suivante, on retrouve le morceau SQL de l'attribut formula :

SELECT   top 20 individu2_ID            AS IND1_3_0_,
                individu2_nom           AS ind4_3_0_,
                individu2_prenom        AS ind5_3_0_,
                individu2_email         AS ind7_3_0_,
                individu2_etab_id       AS etab12_3_0_,
                (SELECT etabNOMALPHA
                 FROM   ETABLISSEMENT etab
                 WHERE  etabID = individu2_ETAB_ID) AS formula0_0_
FROM    INDIVIDU individu2_
ORDER BY (SELECT etabNOMALPHA
          FROM   ETABLISSEMENT etab
          WHERE  etabID = individu2_ETAB_ID) ASC

ce type de propriété peut être également utile en tant que champ calculé, un count sur un champ d'une table externe sans pour autant avoir une collection mappée. Bien entendu, les formula sont à utiliser avec parcimonie, étant donné que c'est du pur SQL, à utiliser sur des tables de références.

Noop

A l'inverse, on pourrait avoir une classe Etablissement qui ne contiendrait pas de propriété Individus (dans le sens POCO donc non mappée) mais qui serait utile d'avoir pour effectuer des sélections : donne-moi les établissements qui ont plus de 200 individus intérimaires. Au niveau de la collection, cela rejoint ce billet qui parlait de ce type de set.

Le type d'accès noop (ou none) sur une propriété permet cela : pouvoir effectuer une requête sur une propriété non mappé dans une classe.

Par exemple, dans le .hbm, on pourrait avoir une collection one-to-many qui nous donne les intérimaires d'un établissement, les individus intérimaires seraient de type 3, une collection qui se traduit par :

<bag  name="Interim" access="noop"
           where="(typind = 3)">
     <key column ="ETAB_ID"/>
     <one-to-many class="MyNamespace.Individu, MyAssembly"/>
 </bag>

dans la classe Etablissement, la propriété Interim n'existe pas.

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" auto-import="true" >
  <class name="MyNamespace.Etablissement, MyAssembly" table="ETABLISSEMENT">
    <id name="ID" column="ID" type="System.Int32" access="property">
      <generator class="identity"></generator>
    </id>
    <property name="EtabNom" column="NOMALPHA" not-null="true" type="System.String" access="property"/>
    <bag name="Interim"
         where="(typind = 3)"
         access="noop">
      <key column ="ETAB_ID"/>
      <one-to-many class="MyNamespace.Individu, MyAssembly"/>
    </bag>
  </class>
</hibernate-mapping>

cela permet ensuite d'effectuer des requêtes HQL, les établissements ayant plus de 200 intérimaires :

var etabs = session.CreateQuery("
  from Etablissement etab  
  where size(etab.Interim) > 200").List<Etablissement>();

qui se traduit en la requête SQL :

SELECT etablissem0_ID       AS ETABCONS1_4_,
          etablissem0_NOMALPHA AS ETABCONS2_4_
   FROM   ETABLISSEMENT etablissem0_
   WHERE  (SELECT count(interim1_ETAB_ID)
           FROM INDIVIDU interim1_
           WHERE etablissem0_ID = interim1_ETAB_ID
           AND ((interim1_typind = 3))) > 200

de façon identique, à utiliser avec prudence, sous peine de rompre l'abstraction que nous avons avec le SQL.