Introduction au mapping Object/Relationnel

Christian Peyrusse

Dans cet article nous aborderons l'architecture ObjectSpaces, vous saurez définir un mapping entre nos objets et nos tables, récupérer des données à l'aide du langage de requêtage OPath, utiliser les objets du modèle ObjectSpaces, ObjectReader, ObjectSet et ObjectQuery. Nous verrons pour terminer comment mettre en œuvre la persistance de données.

Commentez

- Titre : Introduction au mapping Object/Relationnel

- Auteur : Christian Peyrusse

- Parution : 23 août 2004

- Licence : Article écrit pour la revue « Programmez » par Christian Peyrusse - Toute reproduction même partielle doit être soumise à l'accord de l'auteur.

Sommaire

- I. Introduction

- II. Fonctionnement d'ObjectSpaces

- III. Quand utiliser ObjectSpaces ?

- IV. L'architecture d'ObjectSpaces

- V. Définition du Mapping objet/relationnel

- VI. Le langage de requêtage OPath

- VII. La récupération d'objets d'une source de données

- VII-A. Les objets ObjectSpace, ObjectQuery et ObjectReader

- VII-B. L'objet ObjectSet

- VIII. La gestion des transactions

- IX. La suppression des objets

- X. Conclusion

- XI. Note de la rédaction de Developpez.com

I - Introduction

La plupart des applications sont actuellement développées en utilisant un langage objet et une base de données relationnelles. Celle-ci est chargée d'assurer le stockage de données de manière persistante, indépendamment des programmes qui les traitent, alors que le modèle objet gère des objets du monde réel en y associant des données et leur comportement. Dès lors la cohabitation de ces deux mondes pose un certain nombre de problèmes tels que la persistance des données, la représentation de données complexes (hiérarchies, graphes) ou le passage systématique d'un modèle à un autre. Ces problèmes sont résolus grâce à la mise en œuvre de techniques de transformation de modèles objets en modèles relationnels et vice versa, nommées Mapping objet/relationnel (ORM).

Il existe actuellement sur le marché plusieurs solutions de Mapping objet/relationnel. Nous consacrons cet article à celle proposée par Microsoft sur la plate-forme .Net portant le nom d'ObjectSpaces, une nouvelle fonctionnalité qui sera diffusée avec la sortie de Visual Studio 2005. ObjectSpaces est une couche abstraite qui se positionne entre la logique applicative et une source de données. Elle permet au développeur d'accéder et de manipuler des données en tant qu'objets sans connaître la structure de la base de données correspondante. La récupération des objets d'une source de données et la persistance de ces objets sont assurées sans avoir à écrire une seule ligne de code SQL.

Dans cet article nous aborderons l'architecture ObjectSpaces, vous saurez définir un mapping entre nos objets et nos tables, récupérer des données à l'aide du langage de requêtage OPath, utiliser les objets du modèle ObjectSpaces, ObjectReader, ObjectSet et ObjectQuery. Nous verrons pour terminer comment mettre en œuvre la persistance des données.

II - Fonctionnement d'ObjectSpaces

ObjectSpaces offre une couche de haut niveau se situant au dessus d'ADO.Net qui assure une séparation entre la logique métier de la logique d'accès aux données.

Ce produit est constitué :

- d'un framework de persistance ;

- d'un outil de Mapping objet/relationnel intégré à l'environnement de développement qui permet de définir des schémas XML assurant la correspondance entre les objets et les tables d'une base de données.

Le développeur va désormais instancier des classes du modèle ObjectSpace et non plus ADO.Net et leur fournir des objets personnalisés qui contiendront les données à traiter. Le moteur ObjectSpace se charge de traduire les requêtes sur ces objets en requêtes SQL et d'appliquer les modifications sur ces objets vers les tables correspondantes en base de données. De même, en cas de recherche de données, le moteur se charge de les récupérer et de les stocker dans des instances d'objets .Net. Il s'appuie pour réaliser ces opérations sur des métadonnées XML stockées dans un fichier de mappage qui est transmis au constructeur de la classe ObjectSpace. Celui-ci mappe les objets relationnels avec les objets .Net et les types relationnels avec les types .Net. Nous verrons dans la suite de cet article comment définir notre Mapping objet/relationnel.

III - Quand utiliser ObjectSpaces ?

Il est recommandé d'utiliser cet outil lorsque vous disposez d'une couche métier importante et que vous avez un modèle objet à persister sur un espace de stockage.

ObjetSpace exploite de nombreux concepts de Mapping objet/relationnel que je souhaite vous exposer :

- il supporte des relations 1-1, 1-n, n-n entre les classes ;

- il gère les transactions (les méthodes BeginTransaction, Commit et Rollback de l'objet ObjectSpace) ;

- il gère la concurrence d'accès de manière optimiste et lève automatiquement une exception typée (PersistenceException) permettant au développeur d'effectuer les traitements correspondants ;

- il propose un langage de requêtage OPath permettant d'effectuer des recherches dans des objets comme le ferait XPath pour des documents XML ;

- il propose un mécanisme de chargement de données à la demande portant le nom de Delay Loading (lazy loading). Cela signifie que si un objet parent est accédé mais pas un objet fils, les objets fils ne sont pas chargés. Ceci se traduit par un gain en performance et en consommation mémoire. Si un objet fils est accédé alors, il sera chargé à la demande ;

- il est non intrusif avec le modèle objet existant.

Maintenant que les fondements ont été décrits, regardons un peu plus en détail l'architecture d'ObjectSpaces.

IV - L'architecture d'ObjectSpaces

Les classes présentées dans le schéma d'architecture ci-dessus sont incluses dans l'espace de nom System.Data.ObjectSpaces. La classe principale de cette architecture est la classe ObjectSpaces. Elle permet de récupérer les données d'une source, d'identifier les objets à persister, et de contrôler les transactions. Elle s'appuie sur d'autres classes :

Classe

Description

ObjectReader

Propose un flux d'objets en lecture seulement qui résulte de l'exécution d'un ObjectQuery sur une source de données

ObjectSet

Correspond à une collection d'objets

Mapping Schema

Identifie comment les propriétés des objets sont mappées aux champs d'une source de données

ObjectSources

Propose des informations de connexion vers une source de données

ObjectQuery

Correspond à une requête d'un objet

ObjectEngine

ObjectContext

Utilisé pour identifier et gérer les versions des objets afin d'assurer, entre autres, la gestion de la concurrence

ObjectExpression

Utilisé pour précompiler des requêtes à destination de l'ObjectEngine

Nous allons, dans la suite de cet article, utiliser ces objets et préciser leur fonctionnement en prenant comme exemple la base de données NorthWind fournie avec SQL2000. Commençons par décrire notre modèle objet.

V - Définition du Mapping objet/relationnel

Dans notre exemple, nous allons définir le mapping de persistance entre nos objets métiers Client et Commandes et les tables Customers et Orders de la base de données NorthWind.

Afin d'assurer ce mapping, ObjectSpaces se base sur trois fichiers XML.

- Object Schema Document (OSD) :
ce fichier défini les objets métiers, leurs propriétés ainsi que les relations entre les différentes classes.

- Relational Schema Document (RSD) :
ce fichier contient des informations sur les tables du modèle relationnel, les champs de chaque table et les relations entre elles.

- Mapping Schema Document (MSD) :
ce fichier fait référence aux deux autres fichiers et décrit les relations entre le schéma RSD et OSD. Ainsi il va décrire dans notre exemple que la classe Client est mappée à la classe Customers et que le champ Nom correspond au champ CompanyName.

Nous utilisons pour constituer ces fichiers l'éditeur de schéma graphique proposé dans Visual Studio.Net 2005.

Voici un extrait du fichier généré :

1. <m:Mappings>

2. <m:Map Source="Northwind.dbo.Customers" Target="Client">

3. <m:FieldMap SourceField="CustomerID" TargetField="Code" />

4. <m:FieldMap SourceField="CompanyName" TargetField="Nom" />

5. <m:FieldMap SourceField="Phone" TargetField="Telephone" />

6. <m:FieldMap SourceField="City" TargetField="Ville" />

7. <m:RelationshipMap Source="Northwind.FK_Orders_Customers" Target="Commandes" />

8. </m:Map>

9. <m:Map Source="Northwind.dbo.Orders" Target="Commande">

10. <m:FieldMap SourceField="OrderID" TargetField="Identifiant" />

11. <m:FieldMap SourceField="OrderDate" TargetField="DateCommande" />

12. </m:Map>

13. </m:Mappings>

VI - Le langage de requêtage OPath

À l'instar de SQL, pour interroger une base de données, Microsoft a créé un langage de requêtage portant le nom de OPath. Comme pour du SQL ou des requêtes XPath, les requêtes OPath sont des chaînes de caractères utilisées pour récupérer un sous-ensemble d'objets d'une source de données.

L'utilisation du prédicat [] permet de filtrer les données. L'utilisation du point (.) permet de naviguer dans les relations d'un objet.

Exemples de requête OPath.

Orders.Details.Quantity > 50

Filtre sur les commandes dont la quantité d'un des articles est > 50.

Orders[ShippedDate > RequiredDate]

Liste des objets commandes pour lesquels le champ ShippedDate est supérieur au champ RequiredDate.

Orders[Freight > 1000].Details.Quantity > 30

Filtre sur les commandes dont la quantité d'un des articles est > 30 mais seulement pour les objets commandes dont le champ Freight est supérieur à 1000.

VII - La récupération d'objets d'une source de données

VII-A - Les objets ObjectSpace, ObjectQuery et ObjectReader

La première étape pour récupérer des données consiste à instancier un objet de type ObjectSpace. Cet objet prend comme paramètres un schéma XML (client.msd) et une connexion vers une source de données. Ensuite, il faut préparer la requête souhaitée en utilisant un objet Query et en lui indiquant le type d'objet à récupérer et la requête OPath à exécuter. Enfin, il ne reste plus qu'a exécuter la requête via un objet de type ObjectReader et la méthode GetObjectReader. L'objet ObjectReader permet de récupérer le résultat d'un ObjectQuery sous la forme d'un flux d'objets forward-only. Nous parcourons ce flux selon deux méthodes équivalentes (foreach loop ou while loop).

1. Imports System.Data.ObjectSpaces

2. …

3. Dim ospace As ObjectSpace

4. Dim oquery As ObjectQuery(Of Client)

5. Dim oreader As ObjectReader(Of Client)

7. ospace = New ObjectSpace("..\client.msd", New SqlClient.SqlConnection("Datasource=(local);User ID=sa;Initial Catalog=Northwind"))

9. oquery = New ObjectQuery(Of Client)("Client.Nom LIKE 'A%'")

10. oreader = ospace.GetObjectReader(oquery)

12. Dim c As Client

13. For Each c In oreader

14. lb.Items.Add("Nom : " + c.Nom + " Ville : " + c.Ville)

15. Next

17. oreader = ospace.GetObjectReader(oquery)

19. Do While oreader.Read()

20. c = CType(oreader.Current, Client)

21. lb.Items.Add ("Nom : " + c.Nom + " Ville : " + c.Ville)

22. Loop

23. oreader.Close()

Voici à l'exécution le résultat de notre requête.

VII-B - L'objet ObjectSet

ObjectSpace propose également un autre objet permettant de récupérer le résultat de l'exécution d'un ObjetQuery, il s'agit de l'objet ObjectSet. Il stocke en mémoire les objets issus d'une source de données. Il conserve en mémoire les valeurs d'origine de champs permettant ainsi la gestion de la concurrence optimiste. Il peut également être associé à des DataControl afin d'assurer du DataBinding. Pour ceux qui sont familiers d'ADO.Net, il s'apparente à un objet DataSet.

Voici la requête précédente modifiée afin d'utiliser un objet ObjectSet.

1. …

2. gquery = New ObjectQuery(Of Client)("Client.Nom LIKE 'A%'")

3. oset = ospace.GetObjectSet(oquery)

5. Dim c As Client

7. For Each c In oset

8. lb.Items.Add("Nom : " + c.Nom + " Ville : " + c.Ville)

9. Next

Imaginons que nous souhaitions récupérer des informations sur un client dont le numéro de téléphone est le '0621-08460' et obtenir la liste de ses commandes.

Pour ce faire, il faut réaliser des modifications dans notre ObjectQuery. Tout d'abord, modifions notre requête OPath en lui indiquant le numéro de téléphone, puis en complétant le paramètre Span qui indique à quelle profondeur de la hiérarchie d'objets naviguer. Dans notre cas, nous voulons que les commandes soient également retournées à l'exécution de la requête. Il ne reste plus qu'a parcourir la liste des commandes de notre client en utilisant un For Each.

1. …

2. oquery = New ObjectQuery(Of Client)("Client .Telephone='0621-08460'", "Commande")

3. oreader = ospace.GetObjectReader(oquery)

5. Dim client As Client

6. Dim commande As Commande

8. For Each client In oreader

9. lb.Items.Add("Nom : " + client.Nom + " Ville : " + client.Ville)

10. For Each commande In client.Commandes

11. lb.Items.Add("N°Commande : " + commande.Identifiant + " Date : " +

12. Convert.ToString(commande.DateCommande))

13. Next

14. Next

16. oreader.Close()

VIII - La gestion des transactions

ObjectSpace utilise un modèle de gestion de la concurrence « optimiste » pour gérer les mises à jour vers une source de données. Il conserve une copie des valeurs d'origine des propriétés des objets à persister et peut, en cas de concurrence d'accès, lever une exception de Type « Persistence Exception ». Il est ainsi possible de récupérer une référence vers l'objet à l'origine de cette exception et assurer une gestion d'erreur personnalisée.

L'objet ObjectSpace propose également les fonctionnalités classiques assurant la gestion des transactions (BeginTransaction, Commit, Rollback). L'écriture des données d'un objet vers une source de données est permise par la méthode PersistChanges qui prend en paramètre un objet ou une collection d'objets à persister.

Pour vérifier le fonctionnement de la méthode PersistChanges, nous avons modifié les noms de l'objet Client

« Around the Horns » et « Around the Horn » et exécuté le code suivant.

1. Try

2. ospace.BeginTransaction()

3. ospace.PersistChanges(oset)

4. ospace.Commit()

5. Catch err As PersistenceException

6. ospace.Rollback()

7. Dim pe As PersistenceError

8. For Each pe In err.Errors

9. ' Gestion d'erreur personnalisée

10. Next

11. End Try

Voici la requête SQL générée par ObjectSpace pour persister l'objet que nous avons capturé à l'aide du général de profils SQL2000. Nous constatons bien que l'ordre SQL va effectuer une mise à jour du nom de la compagnie AROUT (CustomerId) à « Around the Horn ».

1. SET TRANSACTION ISOLATION LEVEL READ COMMITTED;BEGIN TRANSACTION

3. exec sp_executesql N'

4. Update TO Set TO.[CompanyName]=@C4

5. from [N orthwind].[dbo].[Customers] TO

6. Where (({{TO.[CustomerID] Is Null) and (@00 Is Null)) or and (((TO.[PostalCode] Is Null) and (@021 Is

7. Null)) or TO.[PostalCode]=@021) and (({(TO.[CompanyName] Is Null) and (@03 Is Null)) or TO.[CompanyName]=@03) and

8. (({TO.[City] Is Null) and (@015 Is Null)) or TO.[City]=@015) and (({TO.[Phone] Is Null) and (@027 Is Null)) or

9. TO.[Phone]=@027);

11. Declare @r int, @e int;Set @r=@@ROWCOUNT; Set @e=@@ERROR;If @e = 0 Begin if @r = 0 RAISERROR("No rows

12. affected.",16,1);If @r > 1 RAISERROR(Multiple rows affected.",16,1);End', N@C4 nvarchar(15),@00 nchar(5),@021

13. nvarchar(7),@03 nvarchar(16),@015 nvarchar(6),@027 nvarchar(14), @C4 = N'Around the Horn', @00 = NAROUT', @021

14. = NWA1 1DP', @03 = N'Around the Horns', @015 = N'London', @027 = N'(171) 555-7788

16. COMMIT TRANSACTION

IX - La suppression des objets

Pour supprimer un objet d'une source de données, il faut d'abord sélectionner cet objet, le marquer comme élément à supprimer (via la méthode MarkForDeletion) et enfin assurer sa persistance.

1. Try

2. Dim c As Client

3. c = oset.Item(0)

5. ospace.BeginTransaction()

6. ospace.MarkForDeletion(c)

7. ospace.PersistChanges(oset)

8. ospace.Commit()

9. Catch err As PersistenceException

10. ospace.Rollback()

11. Dim pe As PersistenceError

12. For Each pe In err.Errors

13. ' Gestion d'erreur personnalisée

14. Next

15. End Try

X - Conclusion

Nous vous avons fait découvrir quelques-unes des fonctionnalités d'ObjectSpaces. Cet outil peut répondre aux besoins d'applications qui ont implémenté une logique métier importante et souhaitent bénéficier d'un framework de persistance .Net. Certes, la version utilisée est en bêta-test, il faudra attendre la version Release pour pouvoir l'utiliser en production. Cet outil présente bien la plupart des concepts de base du Mapping objet/relationnel (requêtage, persistance, lazy loading, transactions, gestion de la concurrence d'accès). Un regret tout de même : à ce jour, ObjectSpaces ne fonctionne qu'avec SQLServer 2000 et « Yukon » (la future version de SQLServer), nous espérons que cette technologie sera étendue à d'autres bases de données relationnelles.

XI - Note de la rédaction de Developpez.com

Nous tenons à remercier Winjerome pour la mise au gabarit et jacques_jean pour la relecture orthographique.