I. Présentation▲
Nombreux logiciels ou applications nécessitent la manipulation d'objets ou de données. Ces données doivent donc être stockées quelque part et il devient donc indispensable d'utiliser les bases de données. Le serveur d'application doit donc être capable de se connecter à ces dernières et d'échanger des flux de données dans un certain langage spécifique au SGDB.
Pour faire le lien, en ASP.NET il existe plusieurs possibilités :
- l'Api ODBC (Open DataBase Connectivity). Il s'agit d'un format défini par Microsoft permettant la communication entre des clients bases de données fonctionnant sous Windows et les SGBD du marché. Cette méthode est universelle et fonctionne toujours, mais elle n'est pas très efficace ;
- le provider fourni par le Framework .NET. Beaucoup plus efficace, mais pas optimal ;
- le provider dédié fourni par la société qui a créé le logiciel de base de données. Solution qui permet d'obtenir les meilleurs résultats en termes de performance.
Le principal problème de ces solutions est la dépendance à la base de données. En effet, si demain, le choix est fait de changer le serveur de base de données, il faut reprendre tout le code pour modifier les requêtes pour qu'elles correspondent au nouveau SGBD (passer des connexions Oracle, aux connexions MySql par exemple).
Dans ce but, Microsoft a développé un bloc applicatif nommé « Data Access Application Block » (DAAB) au sein de « l'Enterprise Library ». Il s'agit d'une couche logicielle qui se trouve au-dessus de la couche ADO.NET et qui gère tous les flux avec les différentes bases de données. Il propose 3 fournisseurs de base de données : SqlDatabase (SqlServer) OracleDatabase (Oracle) et Db2Database (Db2); mais il n'est pas prévu de fournisseur pour MySql.
Organisation du Data Access Application Block
(Source : http://www.microsoft.com/france/msdn/donnees/daab.mspxArticle sur le DAAB)
La classe de base abstraite Database définit l'interface commune et fournit la majeure partie de l'implémentation des méthodes d'accès aux données. Les classes SqlDatabase, OracleDatabase et Db2Database sont dérivées de la classe Database. Elles fournissent à leurs systèmes de serveur de base de données respectifs les méthodes incluant les fonctionnalités courantes (mais implémentées de façon différente selon la base de données), ainsi que des fonctionnalités propres au système de base de données considéré.
Le DAAB a donc été conçu pour résoudre les problèmes évoqués au début de cette présentation en fournissant une interface simple et profilée pour l'accès des données et en soustrayant l'information du fournisseur dans un fichier séparé de configuration. En fournissant un certain nombre de méthodes pour accéder aux données, il va aussi réduire sensiblement le code. En garantissant que toutes les fonctions aient un comportement identique quel que soit le fournisseur de base de données, le DAAB permet la généricité des tâches et donc d'être indépendant du fournisseur de bases de données.
II. Procédure d'installation▲
La première étape est de télécharger l'Enterprise Library sur le site de Microsoft. Pour le développement sous ASP.NET 2.0, il faut prendre la version correspondant au Framework 2.0. La version choisie pour cette présentation est celle datant de janvier 2006. Au mois de juillet 2008, le lien était : Télécharger Enterprise LibraryTélécharger Enterprise Library
Il est également possible de ne télécharger que le Data Access Application Block et non toute la librairie à cette adresse :
Télécharger le DAAB.
Une fois téléchargé, il suffit d'exécuter le fichier pour lancer la procédure d'installation. Il est conseillé de laisser les choix par défaut.
Une fois l'installation faite, l'ensemble des librairies sont compilées et sont disponibles dans le dossier C:\Program Files\Microsoft Enterprise Library January 2006\bin
III. Paramétrage▲
L'ensemble des paramètres sont des données XML, qui doivent se trouver dans le fichier « web.config ».
Pour paramétrer, il est possible d'utiliser l'application EntLibConfig.exe que l'on trouve dans le répertoire C:\Program Files\Microsoft Enterprise Library January 2006\bin ou de paramétrer soi-même le fichier web.config.
Pour utiliser le logiciel :
- lancer le programme en double-cliquant sur l'exécutable EntLibConfig.ex ;
créer une nouvelle application (file -> New Application). Un « Application Configuration » est créé ; - créer un nouveau « Data Acces Application Block »(DAAB).
Pour cela, faire un clic droit sur l'application venant d'être juste créée, choisir « New » puis Data Access Application Block, ou cliquer sur « Action » dans la barre des tâches puis « New » avant de choisir Data Access Application Block.
Enterprise Library Configuration
Un DAAB est créé ainsi qu'une « Connection String ».
Configuration - Connection Strings
Il y a donc un onglet « Connection Strings » qui est composé pour le moment d'une seule « Connection String ». Nous allons donc modifier cette chaîne de connexion pour qu'elle corresponde à notre application. - Modifier les différentes informations :
i. Name : le nom que vous voulez donner à votre connexion,
ii. ProviderName : le nom du fournisseur de la base de données. À vous de choisir dans la liste proposée, celui que vous voulez utiliser,
iii. Database : la base de données dans laquelle vous allez accéder aux informations,
iv. Server : serveur qui héberge la base de données,
v. Integrated security : paramètre de sécurité de la connexion.
Création d'une Connection String
Configuration d'une Connection String
Il est possible de créer plusieurs « Connection String » si vous utilisez plusieurs bases de données. Pour en créer une nouvelle, il suffit de faire un clic droit sur « Connection Strings » de choisir « new », « Connection String » puis de remplir à nouveau les champs.
En cliquant sur « Data Access Application Block » il est possible de choisir quelle Connection String on veut par défaut parmi celles qui ont été créées ; - Sauver le fichier de configuration sous le nom web.config.
On obtient alors un fichier XML de ce genre :
<?xml version="1.0"?>
<configuration>
<configSections>
<section
name
=
"dataConfiguration"
type
=
"Microsoft.Practices.EnterpriseLibrary.Data.Configuration.DatabaseSettings,
Microsoft.Practices.EnterpriseLibrary.Data, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=null"
/>
</configSections>
<dataConfiguration
defaultDatabase
=
"MaConnexionSQL"
/>
<connectionStrings>
<add
name
=
"MaConnexionSQL"
connectionString
=
"Database=MABD;
Server=monserveurOracle;
Integrated Security=no;"
providerName
=
"System.Data.OracleClient"
/>
<add
name
=
"SQLServer"
connectionString
=
"Database=baseSQL;
Server=(local)\SQLEXPRESS;
Integrated Security=no;"
providerName
=
"System.Data.SqlClient"
/>
</connectionStrings>
</configuration>
Nous pouvons remarquer que nous avons les deux fournisseurs de base de données avec la chaîne de connexion. Remarquons aussi l'ajout de la balise « dataConfiguration » avec l'attribut defaultDatabase qui permet de définir une connexion par défaut (mais non obligatoire).
Dans le cas d'une application que j'ai dû développer, ce fichier de configuration ne fonctionnait pas et j'ai dû le reprendre pour me connecter aux bases de données de mon entreprise, à la fin j'ai obtenu la configuration suivante :
<?xml version="1.0"?>
<configuration>
<configSections>
<section
name
=
"dataConfiguration"
type
=
"Microsoft.Practices.EnterpriseLibrary.Data.Configuration.DatabaseSettings,
Microsoft.Practices.EnterpriseLibrary.Data, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=null"
/>
</configSections>
<dataConfiguration
defaultDatabase
=
"Oracle"
/>
<connectionStrings>
<add
name
=
"Oracle"
connectionString
=
"Integrated Security=no;
data source=oracle.**********.fr;
User Id=dev;
Password=dev;"
providerName
=
"System.Data.OracleClient"
/>
<add
name
=
"SQLServer"
connectionString
=
"server=localhost;
database=dev;
Integrated Security=no;
User Id=dev;
Password=dev;"
providerName
=
"System.Data.SqlClient"
/>
</connectionStrings>
<system.web>
<compilation
debug
=
"true"
>
</compilation>
</system.web>
</configuration>
Les principales modifications sont l'ajout d'un User Id et du Password pour se connecter au serveur. Pour ma connexion Oracle, j'ai un data source alors que pour ma connexion SQLServer, il me faut rentrer le serveur et le nom de la base de données.
Notre fichier de configuration est maintenant prêt, il nous reste donc à faire le code ASP.NET qui va se connecter à la base de données que l'on désire et effectuer l'ensemble des requêtes.
IV. Connexions et requêtes à la base de données▲
Le langage que l'on va utiliser pour créer nos requêtes n'est pas le langage d'Oracle, de DB2 ou encore SQLExpress. Il s'agit d'un langage SQL commun.
IV-A. Requêtes sans paramètre▲
Voyons un exemple pour se connecter et faire appel aux données.
Ce code source récupère toutes les informations (id, titre, contenu, date, flag) se trouvant dans une base de données et les ajoute dans une liste.
using System;
using System.Data;
using System.Drawing;
using System.Web;
using Microsoft.Practices.EnterpriseLibrary.Data;
using System.Data.Common;
.
.
.
/// <summary>
/// Renvoie une liste de toutes les informations contenues dans la base de donnée
/// </summary>
/// <returns>list d'objet Information</returns>
public
ArrayList AllInformations
(
)
{
ArrayList list =
new
ArrayList
(
);
Database db =
DatabaseFactory.CreateDatabase
(
"Oracle"
);
string sqlQuery =
"SELECT * FROM portail_infos"
;
DbCommand command =
db.GetSqlStringCommand
(
sqlQuery);
IDataReader reader =
db.ExecuteReader
(
command);
while
(
reader.Read
(
))
{
Information inf =
new
Information
(
reader.GetInt32
(
0
),
reader.GetString
(
1
),
reader.GetString
(
2
),
reader.GetDateTime
(
3
),
reader.GetBoolean
(
4
));
list.Add
(
inf);
}
return
list;
}
Explication :
Database db =
DatabaseFactory.CreateDatabase
(
"Oracle"
);
Cette instruction permet la connexion à la base de données. Le paramètre « Oracle » spécifie à quelle base de données nous voulons nous connecter. Ici je veux donc me connecter à ma base de données Oracle dont la connexion String est définie dans le web.config.
Dans le cas où ce paramètre est vide (CreateDatabase()), le DAAB se connectera alors à la base de données par défaut du fichier de configuration.
string sqlQuery =
"SELECT * FROM portail_infos"
;
DbCommand command =
db.GetSqlStringCommand
(
sqlQuery);
IDataReader reader =
db.ExecuteReader
(
command);
while
(
reader.Read
(
))
{
Information inf =
new
Information
(
reader.GetInt32
(
0
),
reader.GetString
(
1
),
reader.GetString
(
2
),
reader.GetDateTime
(
3
),
reader.GetBoolean
(
4
));
list.Add
(
inf);
}
Je crée ma requête SELECT, je l'ajoute au DbCommand puis je l'exécute avec un IDatareader. J'utilise un leader, car par la suite ces informations ne seront qu'affichées à l'écran.
Attention : Vous trouverez dans de nombreux exemples sur Internet l'utilisation de DbCommandWrapper. Cette classe existe seulement dans l'Enterprise Library version 1. Dans la version 2, cette commande est remplacée par DbCommand.
Il est également possible de mettre les données récupérées dans un DataSet :
DataSet infos =
new
DataSet
(
);
db.LoadDataSet
(
command, infos, "infos"
);
Attention : ne pas oublier de rajouter ce qui suit pour que l'application sache quelles dll utiliser :
using Microsoft.Practices.EnterpriseLibrary.Data;
using System.Data.Common;
Dans le cas où l'application sera exportée sur plusieurs machines, je conseille fortement de créer un répertoire dans la solution et de copier les librairies extérieures au Framework nécessaires pour éviter d'installer l'Enterprise Library sur toutes les machines.
Ainsi la référence se fait sur une DLL présente dans le projet et donc aucun risque de DLL manquante.
Pour ajouter la référence dans la solution, faire clic droit sur le nom de la solution, « ajouter une référence ». Aller dans l'onglet « Parcourir » et choisir le fichier « Microsoft.Practices.EnterpriseLibrary.Data.dll » dans le dossier où se trouve votre projet. Dans le cas d'un site Web, un dossier bin sera créé dans la solution du projet avec les différentes dll, dans le cas d'une application, les références seront ajoutées dans le dossier Référence.
IV-B. Requêtes avec paramètres▲
/// <summary>
/// Classe renvoyant Une information selon un ID
/// </summary>
/// <param name="id">ID de l'information</param>
/// <returns>L'information voulue, ou null si inexistante.</returns>
public
Information FetchById
(
int
id)
{
Database db =
DatabaseFactory.CreateDatabase
(
"Oracle"
);
string sqlQuery =
"SELECT * FROM portail_infos where
id=
:IDENTIFIANT";
DbCommand command =
db.GetSqlStringCommand
(
sqlQuery);
db.AddInParameter
(
command, "IDENTIFIANT"
, DbType.Int32,
Convert.ToInt32
(
id));
using (
IDataReader reader =
db.ExecuteReader
(
command))
{
if
(
reader.Read
(
))
{
return
new
Information
(
reader.GetInt32
(
0
),
reader.GetString
(
1
),
reader.GetString
(
2
),
reader.GetDateTime
(
3
),
reader.GetBoolean
(
4
));
}
else
return
null
;
}
}
Comme pour l'exemple précédent, je crée ma requête puis je l'ajoute dans DbCommand. La particularité est que cette requête prend un paramètre qui est :IDENTIFIANT.
Attention si on utilise le serveur de base de données Oracle, le passage de paramètre se fait par l'utilisation des « : ». Par contre pour un serveur de base de données SQLServer, il faut utiliser le « @ ». C'est-à-dire :
string sqlQuery =
"SELECT * FROM portail_infos where
id=
@IDENTIFIANT
";
DbCommand command =
db.GetSqlStringCommand
(
sqlQuery);
db.AddInParameter
(
command, "IDENTIFIANT"
, DbType.Int32,
Convert.ToInt32
(
id));
On s'aperçoit donc que pour la généricité, cette méthode d'ajout de paramètres n'est pas parfaite puisque le symbole identifiant le paramètre est différent.
Pour contourner ce problème, il suffirait d'ajouter directement les paramètres à la requête :
public
Information FetchById
(
int
id)
{
Database db =
DatabaseFactory.CreateDatabase
(
"Oracle"
);
string sqlQuery =
"SELECT * FROM portail_infos where id="
+
id;
DbCommand command =
db.GetSqlStringCommand
(
sqlQuery);
using (
IDataReader reader =
db.ExecuteReader
(
command))
{
.
.
.
.
Le problème de cette solution est qu'elle ouvre la possibilité d'attaque par SQL Injection (Explication ici et là). Elle est donc fortement déconseillée ou alors il faut vérifier chaque paramètre avant de le rajouter dans la requête.
IV-C. Insertion dans la base de données▲
Pour faire une insertion, il faut utiliser l'instruction ExecuteNonQuery. Cette fonction renvoie le nombre de lignes qui ont été affectées par l'exécution de cette méthode.
public
void
inserer
(
)
{
Database db =
DatabaseFactory.CreateDatabase
(
"Oracle"
);
string sqlQuery =
"insert into portail_users values ('toto','toto','toto','toto',012345,toto@toto.fr','toto','toto','toto')"
;
DbCommand command =
db.GetSqlStringCommand
(
sqlQuery);
int
nb =
db.ExecuteNonQuery
(
command);
}
Pour une insertion avec des paramètres, il suffit de faire comme dans l'exemple donné pour une requête SELECT.
IV-D. Procédure sans paramètre▲
Pour exécuter une procédure, il suffit de faire :
public
void
inserer
(
)
{
Database db =
DatabaseFactory.CreateDatabase
(
"Oracle"
);
DbCommand dbCmd =
db.GetStoredProcCommand
(
"insert_infos"
);
db.ExecuteNonQuery
(
dbCmd);
}
IV-E. Procédure avec paramètres▲
Procédure Oracle :
CREATE or REPLACE PROCEDURE insert_infos_param
(id IN number, titre IN varchar2) AS
BEGIN
INSERT INTO portail_infos
VALUES(id,titre,'content3',to_date(sysdate,'dd/mm/yyyy hh24:mi:ss'),1);
END portail_insert_infos_param;
Appel de la procédure :
public
void
inserer
(
)
{
Database db =
DatabaseFactory.CreateDatabase
(
"Oracle"
);
DbCommand dbCmd=
db.GetStoredProcCommand
(
"portail_insert_infos_param"
);
//decouvre tous les parametres necessaires
db.DiscoverParameters
(
dbCmd);
//modifie les parametres pour faire la requete
db.SetParameterValue
(
dbCmd, "id"
, 111
);
db.SetParameterValue
(
dbCmd, "titre"
, "toto a la plage"
);
db.ExecuteNonQuery
(
dbCmd);
}
Concernant les fonctions, les librairies semblent ne pas avoir été bien conçues. En effet, je n'ai pas réussi à trouver un code simple et générique qui fonctionne pour les deux fournisseurs de base Oracle et SQLServer. Il faut donc adapter le code selon le fournisseur de base de données.
Pour Oracle :
CREATE or REPLACE FUNCTION portail_select_param
(id_in IN number)
RETURN varchar
IS
titre_out varchar2(50);
BEGIN
SELECT INTO into titre_out from portail_infos WHERE id=id_in;
RETURN titre_out;
END;
Pour appeler la fonction, il faut savoir que l'on utilise l'instruction :
SELECT nom_fonction FROM dual
Voici un exemple d'appel à la fonction portail_select_param :
Database db =
DatabaseFactory.CreateDatabase
(
"Oracle"
);
string results =
null
;
string query =
"select portail_select_param(:IDENTIFIANT) from dual"
;
DbCommand dbCommand =
db.GetSqlStringCommand
(
query);
db.AddInParameter
(
dbCommand, "IDENTIFIANT"
, DbType.Int32, Convert.ToInt32
(
id));
using (
IDataReader reader =
db.ExecuteReader
(
dbCommand))
{
if
(
reader.Read
(
))
{
results=
reader.GetString
(
0
);
}
else
results=
null
;
}
Pour appeler la fonction, il faut donc de créer une requête SELECT sur le nom de la fonction en ajoutant les paramètres entre parenthèses.
Pour SQLServer :
CREATE FUNCTION portail_select_param(@id_in int)
RETURNS varchar(50)
AS
BEGIN
-- declarer la variable de retour
DECLARE @titre_out varchar(50)
set @titre_out = (SELECT titre from portail_infos where id=@id_in)
-- retour du résultat
RETURN @titre_out;
END
En SQLServer il faut appeler la fonction en faisant un SELECT nom_fonction
Appel de la fonction :
Database db =
DatabaseFactory.CreateDatabase
(
"SQLServer"
);
string results =
null
;
DbCommand dbCommand =
db.GetSqlStringCommand
(
"select dbo.portail_select_param(@IDENTIFIANT)"
);
db.AddInParameter
(
dbCommand, "IDENTIFIANT"
, DbType.Int32, Convert.ToInt32
(
id));
using (
IDataReader reader =
db.ExecuteReader
(
dbCommand))
{
if
(
reader.Read
(
))
{
results =
reader.GetString
(
0
);
}
else
results =
null
;
}
On remarque donc que pour les deux fournisseurs de bases de données, il n'y a pas la même instruction.
Mais il est possible de contourner le problème en créant dans la base de données SQLSERVER, une table DUAL :
CREATE TABLE DUAL
(
DUMMY varchar(1)
)
INSERT INTO DUAL (DUMMY) VALUES ('X')
En rajoutant cette table, nous pouvons alors utiliser la même instruction pour les deux bases de données :
SELECT nom_fonction FROM DUAL ;
V. Méthodes d'accès aux données▲
V-A. ExecuteDataSet▲
Cette instruction permet de retourner la liste de résultats d'une requête dans un DataSet.
V-B. ExecuteReader▲
Exécute la requête et renvoie un SqlDataReader. L'avantage d'un SqlDataReader est que la lecture ne se fait qu'une fois d'avant en arrière sans la possibilité de revenir en arrière pour un gain de performance.
V-C. ExecuteScalar▲
Exécute la requête et renvoie un seul résultat comme un nombre ou une chaîne de caractères. Le type renvoyé est un objet, il faut donc le transtyper.
V-D. ExecuteNonQuery▲
Exécute la requête et ne renvoie pas de données. Utilisé pour les INSERT,UPDATE, DROP…
VI. Conclusion▲
Par le Data Access Application Block, Microsoft propose une couche logicielle pour l'accès aux données de façon générique et se défaire du fournisseur d'accès aux bases de données. Ainsi il est supposé un gain de temps et de code pour le programmeur puisqu'il y a moins de lignes à coder.
Mais dans la réalité, ce n'est pas si simple. La généricité concerne les requêtes de base et les différences entre les fournisseurs de bases de données apparaissent rapidement. Du coup, le code se rallonge puisqu'il faut créer des instructions différentes selon le provider.
Ce rapide tutoriel se base sur la version de janvier 2006, mais depuis, la version 3.1 et 4.0 sont sorties. Elles ne permettent toujours pas de gérer MySql.
Pour utiliser ce fournisseur de base de données, il est possible de le rajouter au code source du projet. CodePlex a également développé un ensemble de classes qui gère de nombreuses fonctionnalités, dont MySql. Je n'ai pas testé toutes les fonctionnalités de ces dernières versions, mais je vous conseille de les utiliser.