Accès Générique
aux Données
avec ADO.NET et le Data Access Application Block par Fabrice Marguerie |
||
Pour accéder aux données depuis vos programmes .NET, le framework fournit une API objet efficace : ADO.NET. Nous ne reviendrons pas sur le support de pilotes natifs ou des pilotes ODBC, qui offrent de nombreux avantages tels que les performances ou la compatibilité. Nous ne reviendrons pas non plus sur les gains apportés par les DataSets, sujet plus ouvert à discussion en raisons des limitations qu'ils imposent dans une conception orientée objet.
Non non, rien de tout cela ici. Nous reviendrons plutôt sur une des limites d'ADO.NET : le manque de généricité. Comme tout logiciel, ADO.NET a quelques défauts de conception. Je vous laisse vous référer à l'article de Sami Jaber pour une bonne introduction au sujet du manque de généricité au sein d'ADO.NET.
Vous avez lu l'article de Sami ? Bien, je vais donc pouvoir vous présenter le contenu du présent article. Il se découpe en deux parties :
Je ne vais pas reprendre l'explication faite par Sami, mais pour faire simple : tout code d'accès aux données en ADO.NET est lié à un moteur de base de données spécifique (et ce aussi simple soit le code écrit). Cela est dû à l'utilisation de classes spécifiques à chaque moteur de base de données.
ADO.NET contient partiellement la solution grâce à l'ensemble
d'interfaces
suivant : IDbConnection, IDbTransaction, IDbCommand, IDataReader,
IDbDataAdapter, IDataParameter.
Il suffit d'aller un peu plus loin en mettant en oeuvre le design
pattern Factory (fabriques de classes)
pour obtenir une solution entièrement générique.
Ce que je vous propose est de voir ensemble la mise en oeuvre d'une bibliothèque de classe nommée Masterline.Data mettant en oeuvre ce principe. Cette bibliothèque couvre tous les aspects de la création de connexions et l'écriture de code d'accès aux données générique. Nous verrons quelques exemples de mise en oeuvre que vous pourrez vous-même reproduire à l'aide des sources livrées à la fin de cette article.
Je vais maintenant introduire deux notions : les Providers, et les DataSources.
Un provider ("fournisseur", notion plus ou moins équivalente aux drivers (pilotes)) permet à ADO.NET de dialoguer avec un moteur de base de données.
Il existe trois types de providers en ADO.NET :
Architecture d'ADO.NET |
Parmi les providers natifs existants, on peut citer le support de :
SQL Server (inclus dans le framework .NET depuis le début)
Oracle (plusieurs versions existent)
une version Microsoft est incluse dans le framework .NET 1.1
une version Oracle existe également: ODP.NET
MySQL
...
Une DataSource représente une source de données.
En ADO.NET, on indique à un provider à quelle base de données on souhaite s'adresser à l'aide de chaînes de connexion (connection strings, dans la langue de Bill Gates).
Exemple de chaîne de connection : "Provider=sqloledb;Data Source=MonServer;Initial Catalog=MaBase;User Id=MonUtilisateur;Password=MonMDP;"
A noter que malheureusement, chaque provider a un format de chaîne de connexion différent ! Pour vous y retrouver vous pouvez consulter http://www.connectionstrings.com/.
Nous appellerons DataSource, la combinaison d'un Provider et d'une chaîne de connection. Ainsi, tout ce dont nous avons besoin pour accéder à une base de données c'est d'une DataSource.
On peut définir des Providers ou des DataSources par deux moyens : par code ou en utilisant un fichier de configuration. Nous allons voir une série de façons de créer des connexions à l'aide de ces objets Provider et DataSource.
Note : Certains providers sont prédéfinis par la bibliothèque Masterline.Data : ODBC, OLE DB, SQL Server et ODP.NET.
Création d'un Provider par code :
const string ConnectionString = "Server=(local);User ID=sa;Password=;"+ database=Northwind;Persist Security Info=true"; Provider provider; provider = new Provider("System.Data, "+ "Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Data.SqlClient.SqlConnection", "System.Data.SqlClient.SqlDataAdapter", "System.Data.SqlClient.SqlCommandBuilder"); using (IDbConnection connection = ConnectionFactory.CreateConnection( provider, ConnectionString)) { connection.Open(); MessageBox.Show("Connection state: "+connection.State.ToString()); } |
Création d'une DataSource par code :
const string ConnectionString = "Server=(local);User ID=sa;Password=;"+ database=Northwind;Persist Security Info=true"; DataSource dataSource; Provider provider; provider = new Provider("System.Data, "+ "Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Data.SqlClient.SqlConnection", "System.Data.SqlClient.SqlDataAdapter", "System.Data.SqlClient.SqlCommandBuilder"); dataSource = new DataSource(provider, ConnectionString); using (IDbConnection connection = ConnectionFactory.CreateConnectionToDataSource(dataSource)) { connection.Open(); MessageBox.Show("Connection state: "+connection.State.ToString()); } |
Utilisation d'un Provider prédéfini :
const string ConnectionString = "Server=(local);User ID=sa;Password=;"+ "database=Northwind;Persist Security Info=true"; using (IDbConnection connection = ConnectionFactory.CreateConnection( DataAccessConfig.Providers[DataAccessConfig.DEFAULTPROVIDER_SqlServer], ConnectionString)) { connection.Open(); MessageBox.Show("Connection state: "+connection.State.ToString()); } |
L'utilisation d'un fichier de configuration .NET permet de définir des Providers et des DataSources à l'extérieur du code.
Utilisation d'une DataSource définie dans le fichier de configuration :
using (IDbConnection connection = ConnectionFactory.CreateConnectionToDataSource("DataSource2")) { connection.Open(); MessageBox.Show("Connection state: "+connection.State.ToString()); } |
Si vous n'utilisez qu'un moteur de base de données ou qu'une source de données, vous pouvez définir un Provider par défaut ou une DataSource par défaut. Cela simplifie le code.
Utilisation d'une DataSource par défaut :
using (IDbConnection connection = ConnectionFactory.CreateConnection()) { connection.Open(); MessageBox.Show("Connection state: "+connection.State.ToString()); } |
Voici le fichier de configuration utilisé pour ces exemples :
|
Après avoir ouvert une connexion, ont peut travailler avec des requêtes et des DataAdapters :
|
|
Note : Les providers prédéfinis et ceux du fichier de configuration sont initialisés par l'appel Masterline.Data.DataAccessConfig.Initialize();
Note : Il existe une solution similaire : Abstract ADO.NET
A ce stade, nous avons nos objets permettant de créer des connexions génériques. C'est très bien, mais l'étape suivante (et somme toute la plus importante) est tout simplement la manipulation des données.
Microsoft a publié il y a quelques temps de cela deux Microsoft Application Blocks for .NET (petit aparté : d'autres sont à venir prochainement, parmi lesquels UIP; cf. niouzes DNG ici et là) :
Celui qui nous concerne aujourd'hui, c'est bien évidemment le Data Access Application Block (DAAB). Vous trouverez de l'information sur cette brique logicielle dans le nouveau site MSDN patterns & practices.
La classe constituant le DAAB (SqlHelper) permet
d'exécuter en une seule ligne de code
des requêtes SQL ou des procédures stockées. Je ne vous exposerai pas
ici les
avantages de cette classe qui peut rendre de grands services, en
particulier si
vous utilisez des procédures stockées.
Le hic,
c'est que le code de cette classe a été spécialement écrit pour SQL
Server.
Il était par conséquent impossible d'utiliser la même approche pour
accéder à un
autre moteur de base de donnée.
Microsoft a fourni, plusieurs mois après (et non officiellement !) des
implémentations du DAAB pour Oracle (classe
OracleHelper)
et OLE DB (classe OleDbHelper) dans le cadre de l'application
exemple
Nile 3.0.
La nécessité de créer des versions spécifiques pour chaque pilote montre
bien
la principale limitation de l'implémentation actuelle de ADO.NET.
Comme on l'a vu, il y a trois types de providers ADO.NET, plus un nombre grandissant de providers natifs. Cela signifie qu'il faudrait avoir quantité de versions : une pour OLE DB, une pour ODBC, et une par provider natif.
La solution que je vous propose est une classe indépendante de tout pilote spécifique : la classe DBHelper.
Nous verrons comment nous pouvons étendre le concept présenté dans la première partie de l'article, en employant nos fabriques de classes pour adapter le DAAB.
Le code de la classe DBHelper provient de la classe SqlHelper. Ont simplement été supprimées les références à des classes liées au provider SQL Server (telles que SqlConnection, SqlCommand, ...).
Voici un exemple de méthode adaptée :
|
|
Les différences ont été marquées en gras.
Hormis l'emploi des interfaces (IDbConnection, IDataParameter,
IDbCommand,
IDbTransaction, IDbDataAdapter), on peut noter deux choses :
|
Ce qu'on peut observer parmi les différences entre les
deux
versions de cette méthode, c'est que la signature de la méthode ne
change
pratiquement pas.
La mise en oeuvre de cette classe DBHelper n'est par conséquent
pas très
différente de la classe SqlHelper initiale.
Voici deux exemples :
|
|
Bien que nous ayons rendu notre code générique, Il est toujours possible d’utiliser les spécificités des moteurs de base de données et des providers dédiés.
Ainsi, on peut par exemple tirer parti de l'événement InfoMessage proposé par SQL Server et la classe SqlConnection en transtypant un objet de type IDbConnection en SqlConnection :
On peut par exemple imaginer une version statique des méthodes de la classe DBHelper qui permet de ne pas avoir à créer d'instance. Libre à vous d'adapter le code et de partager vos suggestions.
Les sources mis à votre disposition contiennent la bibliothèque de classes ainsi que les exemples présentés dans cet article.
ADO.NET souffre d'erreurs de jeunesse. Nous avons vu
qu'il
est possible de palier à ces problèmes de conception. Microsoft se
rattrapera
t'il avec .NET 2 (nom de code Whidbey) avec une solution intégrée
?
On est également en droit de se demander pourquoi Microsoft n'a
pas fournit une version du Data Access Application Block
générique plutôt
qu'une version différente par provider !
Note : Les classes présentées ici font partie du framework .NET de la société Masterline.
Auteur : Fabrice Marguerie
Copyright © Mai 2003
Fabrice
Marguerie est architecte .NET chez Masterline.
Fabrice intervient sur des projets au forfait ou en régie aussi bien que
sur des
missions de conseil. Il a conçu et réalisé le framework .NET de
Masterline. Fabrice rédige
également un weblog en anglais :
http://weblogs.asp.net/fmarguerie/ |
Ressources
Article de Sami Jaber : Développez générique avec ADO.NET.
Site d'Abstract ADO.NET.
Exemple Nile 3.0 où se trouvent OracleHelper et OleDbHelper.
Microsoft patterns & practices.
Téléchargez les sources
Bibliothèque de classe + exemples de mise en oeuvre (Sources) : Masterline.Data.zip (27 Ko, Fichier zip contenant la solution VS.NET)