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é :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
<
m
:
Mappings>
<
m
:
Map
Source
=
"Northwind.dbo.Customers"
Target
=
"Client"
>
<
m
:
FieldMap
SourceField
=
"CustomerID"
TargetField
=
"Code"
/>
<
m
:
FieldMap
SourceField
=
"CompanyName"
TargetField
=
"Nom"
/>
<
m
:
FieldMap
SourceField
=
"Phone"
TargetField
=
"Telephone"
/>
<
m
:
FieldMap
SourceField
=
"City"
TargetField
=
"Ville"
/>
<
m
:
RelationshipMap
Source
=
"Northwind.FK_Orders_Customers"
Target
=
"Commandes"
/>
</
m
:
Map>
<
m
:
Map
Source
=
"Northwind.dbo.Orders"
Target
=
"Commande"
>
<
m
:
FieldMap
SourceField
=
"OrderID"
TargetField
=
"Identifiant"
/>
<
m
:
FieldMap
SourceField
=
"OrderDate"
TargetField
=
"DateCommande"
/>
</
m
:
Map>
</
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).
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
Imports
System.Data.ObjectSpaces
…
Dim
ospace As
ObjectSpace
Dim
oquery As
ObjectQuery
(
Of
Client)
Dim
oreader As
ObjectReader
(
Of
Client)
ospace =
New
ObjectSpace
(
"..\client.msd"
, New
SqlClient.SqlConnection
(
"Datasource=(local);User ID=sa;Initial Catalog=Northwind"
))
oquery =
New
ObjectQuery
(
Of
Client)(
"Client.Nom LIKE 'A%'"
)
oreader =
ospace.GetObjectReader
(
oquery)
Dim
c As
Client
For
Each
c In
oreader
lb.Items.Add
(
"Nom : "
+
c.Nom
+
" Ville : "
+
c.Ville
)
Next
oreader =
ospace.GetObjectReader
(
oquery)
Do
While
oreader.Read
(
)
c =
CType
(
oreader.Current
, Client)
lb.Items.Add
(
"Nom : "
+
c.Nom
+
" Ville : "
+
c.Ville
)
Loop
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.
2.
3.
4.
5.
6.
7.
8.
9.
…
gquery =
New
ObjectQuery
(
Of
Client)(
"Client.Nom LIKE 'A%'"
)
oset =
ospace.GetObjectSet
(
oquery)
Dim
c As
Client
For
Each
c In
oset
lb.Items.Add
(
"Nom : "
+
c.Nom
+
" Ville : "
+
c.Ville
)
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
.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
…
oquery =
New
ObjectQuery
(
Of
Client)(
"Client .Telephone='0621-08460'"
, "Commande"
)
oreader =
ospace.GetObjectReader
(
oquery)
Dim
client As
Client
Dim
commande As
Commande
For
Each
client In
oreader
lb.Items.Add
(
"Nom : "
+
client.Nom
+
" Ville : "
+
client.Ville
)
For
Each
commande In
client.Commandes
lb.Items.Add
(
"N°Commande : "
+
commande.Identifiant
+
" Date : "
+
Convert.ToString
(
commande.DateCommande
))
Next
Next
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.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
Try
ospace.BeginTransaction
(
)
ospace.PersistChanges
(
oset)
ospace.Commit
(
)
Catch
err
As
PersistenceException
ospace.Rollback
(
)
Dim
pe As
PersistenceError
For
Each
pe In
err
.Errors
' Gestion d'erreur personnalisée
Next
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 ».
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
SET
TRANSACTION
ISOLATION
LEVEL
READ
COMMITTED
;BEGIN
TRANSACTION
exec
sp_executesql N'
Update TO Set TO.[CompanyName]=@C4
from [N orthwind].[dbo].[Customers] TO
Where (({{TO.[CustomerID] Is Null) and (@00 Is Null)) or and (((TO.[PostalCode] Is Null) and (@021 Is
Null)) or TO.[PostalCode]=@021) and (({(TO.[CompanyName] Is Null) and (@03 Is Null)) or TO.[CompanyName]=@03) and
(({TO.[City] Is Null) and (@015 Is Null)) or TO.[City]=@015) and (({TO.[Phone] Is Null) and (@027 Is Null)) or
TO.[Phone]=@027);
Declare @r int, @e int;Set @r=@@ROWCOUNT; Set @e=@@ERROR;If @e = 0 Begin if @r = 0 RAISERROR("No rows
affected.",16,1);If @r > 1 RAISERROR(Multiple rows affected.",16,1);End'
, N@C4 nvarchar
(
15
)
,@00
nchar
(
5
)
,@021
nvarchar
(
7
)
,@03
nvarchar
(
16
)
,@015
nvarchar
(
6
)
,@027
nvarchar
(
14
)
, @C4 =
N'Around the Horn'
, @00
=
NAROUT', @021
= NWA1 1DP'
, @03
=
N'Around the Horns'
, @015
=
N'London'
, @027
=
N'(171) 555-7788
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.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
Try
Dim
c As
Client
c =
oset.Item
(
0
)
ospace.BeginTransaction
(
)
ospace.MarkForDeletion
(
c)
ospace.PersistChanges
(
oset)
ospace.Commit
(
)
Catch
err
As
PersistenceException
ospace.Rollback
(
)
Dim
pe As
PersistenceError
For
Each
pe In
err
.Errors
' Gestion d'erreur personnalisée
Next
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.