I. Introduction▲
Comme énoncé plus haut, le but de ce tutoriel est de nous permettre de faire une application basée sur une architecture. Je tenais à préciser que ce tutoriel n'est pas le premier du genre pour la simple et bonne raison qu'un auteur a déjà publié trois articles Partie 1, Partie 2 et Partie 3. Au moment de la rédaction, je n'ai nullement l'intention de critiquer ou déprécier son article, seulement le présent tutoriel aborde le même principe de développement, mais d'une autre manière.
II. Préambule▲
Ce qu'il faut retenir, c'est qu'une architecture est élaborée selon les besoins et aspirations de celui qui l'a écrite, j'ai eu à bosser avec un ami sur une architecture compliquée. Pourquoi ? Simplement parce qu'elle répondait à ses exigences. Dans les parties suivantes, nous expliquerons les principes de base d'une architecture, cependant vous devriez obligatoirement vous familiariser à certains mots et concepts tels que la Programmation Orientée, couche métier, couche physique, couche d'accès aux données et bien d'autres. Avant de vous lancer je vous conseille de faire un tour sur ces articles Programmation orientée objet, TSQL. Vous serez ensuite prêt pour vous lancer dans ce tutoriel.
III. Présentation d'un modèle d'architecture▲
La présentation consistera en l'énumération des grandes parties de notre architecture et comment elle fonctionne. Voici donc un schéma qui retrace son fonctionnement.
IV. Explication▲
Selon l'image ci-dessus, nous prenons connaissance de la couche métier, couche d'accès aux données, couche physique et la couche interface.
IV-A. La couche interface▲
Cette couche se matérialise par les interfaces utilisateur (les fenêtres, boutons, les zones de texte). Dans la couche interface, nous ne ferons qu'instancier les méthodes des objets de la couche métier.
IV-B. La couche métier▲
La couche métier contient des méthodes qui seront utilisées par les utilisateurs. Lorsqu'un utilisateur procède par exemple à une insertion ou une modification, ce sont ces méthodes créées dans la couche métier qui seront appelées. Elle permet de définir aussi les règles de validation de formulaire.
IV-C. La couche d'accès aux données▲
Cette couche se scinde en deux parties. Une première qui permet de décrire les entités de notre base de données (comme entité nous pourrons avoir des classes, des structs dont les propriétés devront être identiques à celles de vos tables au niveau de votre base de données) et une deuxième dans laquelle nous écrirons des méthodes qui sont relatives aux bases de données. Dans cette seconde couche, nous écrirons des fonctions qui porteront sur des opérations de base telles que l'insertion, la modification, la sélection et la suppression. L'écriture de ces méthodes dépendra du Système de Gestion de Base de Données. Étant donné que nous utiliserons le langage Csharp et une base de données SQL Server Express, nous utiliserons également des espaces de noms qui sont spécifiques à notre base de données. Pour une base de données SQL Server nous aurons System.Data, System.Data.SqlClient et MySql.Data, MySql.Data.MySqlClient pour une base de données MySQL et vice versa. Vos méthodes dépendront du SGBD que vous utiliserez. Cependant, sachez que les méthodes de la couche d'accès aux données sont créées en fonction des scripts que nous aurons écrits au niveau de notre base de données, la couche physique.
IV-D. La couche physique▲
Elle contient les scripts de création de notre base de données et les opérations du CRUD (Create Read Update and Delete) et d'autres requêtes selon les fonctionnalités de l'application ou les besoins des utilisateurs. De façon plus simple, la couche physique c'est notre Système de Gestion de Base de Données (SGBD).
V. Miniprojet par la pratique▲
Pour mieux appréhender le développement avec une architecture, je vous propose de concevoir une petite application qui nous permettra d'enregistrer des clients avec leurs contacts. La table client possédera les propriétés nom, prénom, téléphone et adresse.
À partir de ce modèle, nous créerons notre base de données sous SQL Server Express avec les scripts suivants :
- Insertion ;
- Modification ;
- Sélection ;
- Suppression ;
- Vérification avant insertion (cette procédure stockée permettra de vérifier les numéros des clients avant leur insertion).
VI. Base de données▲
Ouvrez Microsoft SQL Server et créez la base de données. Faites un clic droit sur Databases et cliquez sur New Database et nommez la base de données architecture.
VI-A. Le script de création de la table ▲
CREATE
TABLE
client
(
id int
primary
key
identity
(
1
,1
)
,--La colonne étant auto-incrémentée
nom varchar
(
20
)
,
prenom varchar
(
30
)
,
tel varchar
(
15
)
,
adresse varchar
(
50
)
)
;
VI-B. Le script de création des procédures stockées▲
Nous allons donc créer nos procédures stockées, elles sont faites au niveau de notre SGBD, pour notre tutoriel, il s'agit de SQL Server Express.
VI-B-I. L'insertion▲
CREATE
PROCEDURE
InsertClient
(
/*Définition des paramètres
en Transact-SQL les variables doivent être précédées du caractère @
*/
@nom varchar
(
20
)
,
@prenom varchar
(
30
)
,
@tel varchar
(
15
)
,
@adresse varchar
(
50
)
)
AS
INSERT
INTO
client
(
nom,prenom,tel,adresse)
VALUES
(
@nom,@prenom,@tel,@adresse)
VI-B-II. La modification▲
CREATE
PROCEDURE
UpdateClient(
--Définition des paramètres de notre procédure
@id int
,
@nom varchar
(
20
)
,
@prenom varchar
(
30
)
,
@tel varchar
(
15
)
,
@adresse varchar
(
50
)
)
AS
UPDATE
Client
SET
nom=
@nom,
prenom=
@prenom,
tel=
@tel,
adresse=
@adresse
WHERE
(
client
.id=
@id)
VI-B-III. La sélection▲
CREATE
PROCEDURE
SelectClient
AS
SELECT
id,nom,prenom,tel,adresse from
client
;
VI-B-IV. La suppression▲
CREATE
PROCEDURE
DeleteClient
(
@Id int
)
AS
DELETE
FROM
client
WHERE
(
client
.id=
@Id)
;
VI-B-V. La vérification▲
CREATE
PROCEDURE
VerifNumeroBeforeInsert
(
@Numero varchar
(
15
)
)
AS
SELECT
COUNT
(*)
as
'Nombre'
FROM
client
WHERE
(
client
.tel=
@Numero)
;
VII. Projet en Csharp▲
Après création des méthodes, nous aborderons la partie code de notre tutoriel. Pour cela, nous allons créer notre solution sous Visual Studio, nommez-la architecture et ensuite vous y ajouterez trois projets de classe nommés respectivement client_metier, client_data, client_structure. À la fin vous devriez avoir une structure comme celle ci-dessous.
VIII. Code▲
Maintenant que notre solution est créée, nous procéderons à l'écriture de nos couches. Pour ce faire, nous commencerons par la couche d'accès aux données. Cette couche se subdivise en deux parties. Une partie qui permettra de décrire les objets (classes ou structures de données) et une deuxième dans laquelle nous définirons les méthodes qui implémenteront les scripts que nous avons précédemment créés au niveau de la base de données.
VIII-A. La description (couche d'accès aux données)▲
Voici un exemple de la structure. Cette partie nous permettra de définir les objets que nous utiliserons dans notre application. Notez que ces objets sont créés en tenant compte des structures des tables de notre base de données. Pour notre minitutoriel, nous utiliserons des struct plutôt que des classes (Classes et structures).
Nous avons commencé par cette partie, car l'écriture des couches métier, les méthodes d'accès aux données en dépend.
VIII-B. Les méthodes d'accès aux données ▲
Pour notre exemple, nous n'avons que cinq méthodes, mais elles peuvent varier selon le projet sur lequel vous travaillerez. Nous allons donc créer nos méthodes, lesquelles serviront d'intermédiaire entre notre application et notre moteur de base de données. Ouvrez votre éditeur, ajoutez une classe au projet client_data que vous nommerez clientdata et nous ajouterons nos codes.
- L'insertion
public
int
insertclient
(
client notreclient)
{
SqlConnection cnx =
new
SqlConnection
(
connexion);
cnx.
Open
(
);
SqlCommand cmd =
new
SqlCommand
(
"insertclient"
,
cnx);
cmd.
CommandType =
CommandType.
StoredProcedure;
SqlParameter prmnom =
new
SqlParameter
(
"nom"
,
SqlDbType.
VarChar,
20
);
prmnom.
Value =
notreclient.
nom;
cmd.
Parameters.
Add
(
prmnom);
SqlParameter prmprenom =
new
SqlParameter
(
"prenom"
,
SqlDbType.
VarChar,
20
);
prmprenom.
Value =
notreclient.
prenom;
cmd.
Parameters.
Add
(
prmprenom);
SqlParameter prmtel =
new
SqlParameter
(
"tel"
,
SqlDbType.
VarChar,
20
);
prmtel.
Value =
notreclient.
tel;
cmd.
Parameters.
Add
(
prmtel);
SqlParameter prmadresse =
new
SqlParameter
(
"adresse"
,
SqlDbType.
VarChar,
20
);
prmadresse.
Value =
notreclient.
adresse;
cmd.
Parameters.
Add
(
prmadresse);
int
resultat =
Convert.
ToInt32
(
cmd.
ExecuteNonQuery
(
));
return
resultat;
}
-
La mise à jour
updateclientSélectionnezpublic
int
updateclient
(
client notreclient){
SqlConnection cnx=
new
SqlConnection
(
connexion);
cnx.
Open
(
);
SqlCommand cmd=
new
SqlCommand
(
"updateclient"
,
cnx);
cmd.
CommandType=
CommandType.
StoredProcedure;
SqlParameter prmid=
new
SqlParameter
(
"id"
,
SqlDbType.
Int,
20
);
prmid.
Value=
notreclient.
nom;
cmd.
Parameters.
Add
(
prmid);
SqlParameter prmnom=
new
SqlParameter
(
"nom"
,
SqlDbType.
VarChar,
20
);
prmnom.
Value=
notreclient.
nom;
cmd.
Parameters.
Add
(
prmnom);
SqlParameter prmprenom=
new
SqlParameter
(
"prenom"
,
SqlDbType.
VarChar,
20
);
prmprenom.
Value=
notreclient.
prenom;
cmd.
Parameters.
Add
(
prmprenom);
SqlParameter prmtel=
new
SqlParameter
(
"tel"
,
SqlDbType.
VarChar,
20
);
prmtel.
Value=
notreclient.
tel;
cmd.
Parameters.
Add
(
prmtel);
SqlParameter prmadresse=
new
SqlParameter
(
"adresse"
,
SqlDbType.
VarChar,
20
);
prmadresse.
Value=
notreclient.
adresse;
cmd.
Parameters.
Add
(
prmadresse);
int
resultat=
Convert.
ToInt32
(
cmd.
ExecuteNonQuery
(
));
return
resultat;
}
-
La suppression
deleteclientSélectionnezpublic
void
deleteclient
(
int
notreclient)//On passe l'identifiant du client en paramètre
{
SqlConnection cnx=
new
SqlConnection
(
connexion);
cnx.
Open
(
);
SqlCommand cmd=
new
SqlCommand
(
"deleteclient"
,
cnx);
cmd.
CommandType=
CommandType.
StoredProcedure;
//Ajout des paramètres
SqlParameter prmid=
new
SqlParameter
(
"id"
,
SqlDbType.
Int,
0
);
prmid.
Value=
notreclient;
cmd.
Parameters.
Add
(
prmid);
//Exécution
cmd.
ExecuteNonQuery
(
);
}
-
La sélection
selectclientSélectionnezpublic
DataTableselectclient
(
){
SqlConnection cnx=
new
SqlConnection
(
connexion);
//Notre connexion à la base de données
cnx.
Open
(
);
//Ouverture de la connexion
SqlCommand cmd=
new
SqlCommand
(
"selectclient"
,
cnx);
//Initialisation de la commande
cmd.
CommandType=
CommandType.
StoredProcedure;
//Ici nous précisons le type de notre commande qui est en fait une procédure stockée
DataTable dt=
new
DataTable
(
);
//Une table de données en mémoire
SqlDataAdapter adaptater=
new
SqlDataAdapter
(
cmd);
adaptater.
Fill
(
dt);
return
dt;
}
- La vérification
/*Cette méthode permettra de vérifier l'existence d'un numéro
C'est-à-dire que lorsque nous ajouterons un nouveau client, nous allons
nous assurer que ce numéro n'est pas déjà utilisé par quelqu'un d'autre*/
public
DataTable verifclientbeforeinsert
(
string
paramNumero)
{
SqlConnection cnx =
new
SqlConnection
(
connexion);
//Notre connexion à la base de données
cnx.
Open
(
);
//Ouverture de la connexion
SqlCommand cmd =
new
SqlCommand
(
"VerifNumeroBeforeInsert"
,
cnx);
//Initialisation de la commande
cmd.
CommandType =
CommandType.
StoredProcedure;
//Ici nous précisons le type de notre commande qui est en fait une procédure stockée
DataTable dt =
new
DataTable
(
);
//Une table de données en mémoire
SqlDataAdapter adaptater =
new
SqlDataAdapter
(
cmd);
adaptater.
Fill
(
dt);
return
dt;
}
IX. Couche métier▲
Pour notre couche métier, nous allons donc décrire nos objets. Cela doit être fait selon les tables de notre base de données. Voici un exemple de notre couche métier.
using
System;
using
System.
Collections.
Generic;
using
System.
Linq;
using
client_structure;
//Notre couche de destruction
using
client_data;
//On inclue notre couche d'accès aux données
using
System.
Text;
namespace
client_metier
{
public
class
clientmetier
{
#region Propriétés publiques
public
int
id
{
get
{
return
_id;
}
set
{
_id =
value
;
}
}
public
string
nom
{
get
{
return
_nom;
}
set
{
_nom=
value
;
}
}
public
string
prenom
{
get
{
return
_prenom;
}
set
{
_prenom =
value
;
}
}
public
string
tel
{
get
{
return
_tel;
}
set
{
_tel =
value
;
}
}
public
string
adresse
{
get
{
return
_adresse;
}
set
{
_adresse =
value
;
}
}
#endregion
#region Propriétés privées
int
_id;
string
_nom;
string
_prenom;
string
_tel;
string
_adresse;
#endregion
}
}
Comme vous le voyez, nous avons deux sortes de déclarations. Celles commençant par le mot-clé public(qui seront accessibles partout) et celles avec le mot private(qui ne seront accessibles que par les objets de la classe). En plus de la description, nous allons également utiliser un objet dans cette couche qui permettra de faire le mapping entre la couche métier et la couche interface. C'est-à-dire que cet objet servira d'intermédiaire en quelque sorte. Dans l'exemple ci-dessous, vous avez la déclaration de notre objet.
#region Objet de mapping
client NotreClient
{
//On déclare donc ces propriétés uniquement en lecture
get
{
client LeClient;
LeClient.
id =
this
.
_id;
LeClient.
nom =
this
.
_nom;
LeClient.
prenom=
this
.
_prenom;
LeClient.
adresse =
this
.
adresse;
LeClient.
tel =
this
.
_tel;
return
LeClient;
}
}
#endregion
Après avoir défini notre objet, nous allons donc écrire les méthodes de notre traditionnel CRUD (entendez par là l'insertion, la lecture, la modification et la suppression).
#region Les méthodes de notre CRUD
//Méthode d'insertion
public
int
Insertclient
(
)
{
unclientdata =
new
clientdata
(
);
return
unclientdata.
insertclient
(
NotreClient);
//La méthode d'insertion retourne un entier
}
//Méthode de modification
public
int
Updateclient
(
)
{
unclientdata =
new
clientdata
(
);
return
unclientdata.
updateclient
(
NotreClient);
}
//Méthode de suppression
public
void
Deleteclient
(
int
idclient)
{
unclientdata =
new
clientdata
(
);
unclientdata.
deleteclient
(
idclient);
}
public
DataTable Selectclient
(
)
{
unclientdata =
new
clientdata
(
);
return
unclientdata.
selectclient
(
);
}
public
int
VerifNumero
(
string
Numero)
{
unclientdata =
new
clientdata
(
);
return
unclientdata.
verifclientbeforeinsert
(
Numero);
}
#endregion
X. Couche interface▲
Ajoutez un projet de type WindowsFormsApplication à votre projet et faites en sorte que votre formulaire ressemble à quelque chose comme l'image ci-dessous.
Nous allons donc procéder aux différentes opérations sur notre base de données.
Pour cela, nous devrons impérativement instancier un objet de la classe métier. Voici donc un extrait du code de notre bouton, mais avant n'oubliez pas d'importer les espaces de noms requis et d'ajouter une référence à notre couche métier (clic droit sur le projet et sélectionner « Ajouter référence » et sélectionner la couche métier) ensuite vous devrez l'inclure comme l'exemple ci-dessous.
using
client_metier;
//Référence à la couche métier
#region Propriétés privées
clientmetier unclient =
null
;
#endregion
X-A. L'insertion et la vérification▲
En ce qui concerne l'insertion, nous devons nous assurer que le numéro entré n'est utilisé par aucun client. Avec le code l'explication sera beaucoup plus facile.
public
void
insertclient
(
string
numero,
string
nom,
string
prenom,
string
adresse)
{
unclient =
new
clientmetier
(
);
int
verif =
unclient.
VerifNumero
(
numero);
//Méthode vérification des numéros avant insertion que nous avons écrite dans la couche métier
if
(
verif ==
0
) //Si vrai alors le numéro n'existe pas
{
//Assignation
unclient.
nom =
nom;
unclient.
prenom =
prenom;
unclient.
tel =
numero;
unclient.
adresse =
adresse;
//Insertion
unclient.
Insertclient
(
);
//Méthode d'insertion
cleanfields
(
);
//Cette méthode vide le contenu des champs
}
else
{
//Sinon on affiche un message d'erreur à l'utilisateur
MessageBox.
Show
(
"Ce numéro existe déjà"
,
"Erreur"
,
MessageBoxButtons.
OK,
MessageBoxIcon.
Error);
}
}
Et pour finir, la méthode d'insertion est appelée derrière le bouton comme suit
private
void
button1_Click
(
object
sender,
EventArgs e)
{
insertclient
(
txttel.
Text,
txtnom.
Text,
txtprenom.
Text,
txtadresse.
Text);
cleanfields
(
);
loaddata
(
);
//Méthode d'affichage des données.
}
X-B. La sélection▲
Pour effectuer une sélection, cela est possible grâce à un espace de noms qui est le system.Data. C'est pourquoi nous avons cette ligne.
using
System.
Data;
Voici la méthode sélection
public
void
loaddata
(
)
{
AllClient =
new
System.
Data.
DataTable
(
);
unclient =
new
clientmetier
(
);
AllClient =
unclient.
Selectclient
(
);
//Notre méthode écrite dans la couche d'accès aux données retourne un DataTable
listView1.
Items.
Clear
(
);
//Suppression de la liste avant d'y afficher les données
foreach
(
DataRow dtr in
AllClient.
Rows)
{
//Le DataRow nous permet de parcourir chaque ligne du DataTable(qui est une table de données en mémoire)
int
id =
0
;
string
nom =
null
;
string
prenom =
null
;
string
tel =
null
;
string
adresse =
null
;
//Nous recueillons les valeurs en fonction du nom des colonnes et nous procédons à l'assignation
id =
(
int
)dtr[
"id"
];
nom =
(
string
)dtr[
"nom"
].
ToString
(
);
prenom =
(
string
)dtr[
"prenom"
].
ToString
(
);
adresse =
(
string
)dtr[
"adresse"
].
ToString
(
);
tel =
(
string
)dtr[
"tel"
].
ToString
(
);
//Fin assignation
ListViewItem itm =
listView1.
Items.
Add
(
nom);
itm.
SubItems.
Add
(
prenom);
itm.
SubItems.
Add
(
tel);
itm.
SubItems.
Add
(
adresse);
itm.
Tag =
id;
}
}
X-C. La modification▲
private
void
btnUpdate_Click
(
object
sender,
EventArgs e)
{
try
{
unclient =
new
clientmetier
(
);
unclient.
id =
(
int
)listView1.
SelectedItems[
0
].
Tag;
//Récupération de l'identifiant
unclient.
nom =
txtnom.
Text;
unclient.
prenom =
txtprenom.
Text;
unclient.
tel =
txttel.
Text;
unclient.
adresse =
txtadresse.
Text;
unclient.
Updateclient
(
);
cleanfields
(
);
//Rafraichissement
loaddata
(
);
}
catch
(
Exception ex)
{
MessageBox.
Show
(
"Erreur vérifiez les champs SVP "
+
ex.
Message);
}
}
X-D. La suppression▲
private
void
btnDelete_Click
(
object
sender,
EventArgs e)
{
clientmetier unclient =
new
clientmetier
(
);
try
{
unclient.
id =
(
int
)listView1.
SelectedItems[
0
].
Tag;
//Récupération de l'identifiant
unclient.
Deleteclient
(
unclient.
id);
//Suppression
cleanfields
(
);
//Rafraichissement
loaddata
(
);
}
catch
(
Exception ex)
{
MessageBox.
Show
(
"Aucune sélection effectuée"
);
}
}
XI. Remerciements▲
Je voudrais remercier mon professeur Monsieur Innocent Apkatou, tous les membres du site en particulier djibril, Lana.Bauer, Viduc, ClaudeLELOUP et f-leb.