Bon, histoire d'être pratique et pour faire suite au code de Graffito, je vous présente ci après un modèle d'architecture d'accès aux données. Il s'agit d'un modèle ultra simplifié, qui n'a pas pour but de transmettre quelque chose de fonctionnel, mais pour but de transmettre une idée, un concept, une démarche. Je n'ai pas le temps de tout détailler, mais cela devrait suffire. Si cela vous séduit, il faudra travailler un petit peu. Mais les résultats et la satisfaction seront au rendez-vous.
Imaginez un fichier C# (ça marche aussi en VB) appelé SqlBase contenant une classe partielle static appelée Sql et implémentant (pour comprendre la suite il faut savoir ce qu'est une classe partielle, voir la doc de votre langage si nécessaire) :
Voici une idée d'implémentation pour les constantes SQL :
- Une méthode GetConnection(), qui retourne une connection correctement initialisée et ouverte. C'est cette méthode qui sera appelée en tout point de l'application dès que l'on aura besoin d'une connexion.
- Tout un ensemble de constantes helpers reprenant le langage SQL
- Tout un ensemble de méthodes helpers pour aider à la création de requêtes sql.
- Tout un ensemble de méthodes de conversion.
Voici un exemple de ce que l'on pourra commencer à faire avec une telle classe :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 public static partial class Sql { public const string SELECT = "SELECT "; public const string DISTINCT = " DISTINCT "; public const string ALL = " * "; public const string FROM = " FROM "; public const string WHERE = " WHERE "; public const string AND = " AND "; public const string OR = " OR "; public const string EQ = " = "; public const string LIKE = " LIKE "; public const string DIFF = " <> "; public const string SUP = " > "; public const string INF = " < "; public const string SUPEQ = " >= "; public const string INFEQ = " <= "; public const string ORDERBY = " ORDER BY "; public const string ASC = " ASC "; public const string DESC = " DESC "; public const string JOIN = " DESC "; public const string LEFTJOIN = " LEFT JOIN "; public const string RIGHTJOIN = " RIGHT JOIN "; public const string ON = " ON "; ... }
Note :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4 SqlCommand com = GetConnection().CreateCommand(); com.Parameters.Add(new SqlParameter(PARAM+CLI_UID,1)); com.CommandText = SELECT+ALL+FROM+CLIENTS+WHERE+CLI_UID+EQ+PARAM+CLI_UID;
Poursuivons la démarche et répondons à la question : d'où viennent les constantes soulignées ci dessus ?
- Nous verrons par la suite d'où viennent les constantes soulignées.
- Notez que de cette façon les requêtes SQL ne sont pas codées en dur. Les erreurs sont ainsi signalées à la compilation.
La réponse est simple. Imaginez un tout petit automate (100 lignes de code maxi), qui parcourt la structure de votre base et génère un fichier c# appelé SqlAutomated implémentant une classe partielle static Sql (la même que ci-dessus) contenant une constante pour chaque table de la base et une constante pour chaque champ de chaque table (a vous de vous débrouiller pour éviter les conflits de noms, soit en travaillant sur la structure de la base, soit en travaillant sur le code généré par l'automate. Mois, j'applique un tri-gramme (3 lettres) unique à chaque table).
Et voilà, à ce stade, nous somme en mesure d'accéder à notre base de donnée, et d'écrire n'importe quelle requête SQL à partir de constantes, et donc de profiter du mécanisme de compilation. En effet, imaginez que vous changiez le nom d'un champ dans la base. Il suffit de relancer l'automate, le fichier de constante va être régénère. En tout endroit ou vous utilisiez l'ancien nom de champs, le compilateur signalera une erreur jusqu'à correction. Nous pourrions déjà commencer à travailler avec ce modèle. Mais, on peut faire mieux.
Pour ne pas perdre notre idée, résumons ce que nous avons :
Dans notre projet :
- SqlBase (implémenté une fois pour toute)
- SqlAutomated (généré par un automate)
A coté de notre projet
- Un petit programme automate (pour avoir des idées sur la façon de créer l'automate, regardez le code de grafito précédent ce post).
Notre réflexion nous amène à nous rendre compte que lorsque nous travaillons sur une table ou sur une autre, nous faisons toujours à peu près la même chose. D'ou l'idée d'implémenter un mécanisme générique que nous pourrons ensuite personnaliser en fonction de chaque table.
Note : L'implémentation qui va suivre sera différente si l'on travail en ASP.NET du au fait que l'on ne maintient pas d'état.
Dans un fichier Table, implémentons une classe Table héritant de System.Data.DataTable. Cette classe pourrait avoir la structure suivante :
Mais me direz vous, cette classe n'est absolument pas fonctionnelle en l'état. Oui, mais puisque nous disposons déjà d'un petit automate qui sait brasser les tables de la base de données, pourquoi ne pas lui demander de faire un petit travail en plus ! En plus de générer le fichier SqlAutomated, il générera un fichier TableAutomated qui contiendra pour chaque table de la base de données une classe TableBase<NomDeLaTable> surchargeant la classe virtuelle Table et :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58 public class Table : DataTable { protected virtual SqlCommand GetSelectCommand() { // Nous parlerons de l'implémentation plus loin } protected virtual SqlCommand GetInsertCommand() { // Nous parlerons de l'implémentation plus loin } protected virtual SqlCommand GetUpadteCommand() { // Nous parlerons de l'implémentation plus loin } protected virtual SqlCommand GetDeleteCommand() { // Nous parlerons de l'implémentation plus loin } private SqlDataAdapter _Adapter = null; protected SqlDataAdapter GetAdapeter() { if(_Adapter == null) { _Adapter= new ...; _Adapter.SelectCommand = GetSelectCommand(); _Adapter.InsertCommand = GetInsertCommand(); _Adapter.UpdateCommand = GetUpdateCommand(); _Adapter.DeleteCommand = GetDeleteCommand(); } return _Adapter; } // Sera utilisée par des méthodes spécialisées effectuant des // requêtes métier autre qu'un simple select tout protected Fill(SqlCommand Command) { SqlAdapter adapter = GetAdapter(); adapter.SelectCommand = Command; adapter.Fill(this); } // Implémentation générale d'une méthode de remplissage public SelectAll() { GetAdapter().Fill(this); } // Un simple appel à cette méthode et la table se met à jour public Update() { GetAdapter().Update(this); } }
A quoi pourait ressembler l'implémentation de chaqune de ces classes ?
- Implémentant le créateur de la classe qui renseignera la collection des colonnes, leur type, clé unique ou pas, null ou non,...
- implémentant les méthodes GetSelectCommand(), GetInsertCommand(), GetUpdateCommand(), GetDeleteCommand().
L'idée c'est que c'est l'automate qui génère tout le code rébarbatif. En plus, au moindre changement dans la base, il suffit de relancer l'automate et instantanément on dispose à nouveau d'un code en parfaite adéquation avec la base de données. A vous d'imaginer l'automate en fonction de votre contexte. On pourrait imaginer qu'il crée aussi un DataSet contenant les tables et les liens,... c'est à vous de voir, c'est à votre imagination d'être au rendez-vous.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36 public class TableBaseClient : Table { public TableClient() { TableName = CLIENTS; // N'oublions pas que l'automate génère les constantes Columns.Add(CLI_UID,typeof(int)...); Columns.Add(CLI_NOM,typeof(string)...); Columns.Add(CLI_PRENOM,typeof(string)...); ... } protected override SqlCommand GetSelectCommand() { SqlCommand com = GetConnection().CreateCommand(); com .CommandText = SELECT + CLI_UID + CLI_NOM + ... + FROM + CLIENTS; return com; } protected override SqlCommand GetInsertCommand() { SqlCommand com = GetConnection().CreateCommand(); com .CommandText = INSERT + INTO + ... return com; } protected override SqlCommand GetUpdateCommand() { ... } protected override SqlCommand GetDeleteCommand() { ... } }
Quoi qu'il en soit, nous disposons maintenant pour chaque table de la base : d'une classe capable de sélectionner les données de la table (SelectAll()) et d'effectuer les modifications apportées (Update()).
Si nous avons du métier à implémenter, nous ne pouvons pas le faire directement sur ces classes car notre travail serait perdu à la prochaine génération de l'automate. Oui mais bon, comme nous partons du principe que notre automate est gentil et qu'il fait tout ce que l'on veut, on pourrait lui demande au passage de penser à générer un fichier C# (si il n'existe pas encore) pour chaque table de la base appelé Table<NomDeTable>, implémentant une surcharge de la classe TableBase<NomDeTable> vide.
Cela pourait donner simplement ça pour table clients :
Dans ce fichier nous pourrons alors implémenter ce que nous voulons, par exemple :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4 public class TableClients : TableBaseClients { }
Résumons ce que nous avons :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11 public class TableClients : TableBaseClients { public SelectByName(string Name) { SqlCommand com = GetSelectCommand(); com.Parameters.Add(new SqlParameter(PARAM+CLI_NOM,Name)); com.CommandText+=WHERE + CLI_NOM+LIKE+PARAM+CLI_NOM; Fill(com); } }
Dans notre projet :
- SqlBase (impémenté une fois pour toute)
- Table (implémente le mécanisme générique de gestion des données d'une table)
- SqlAutomated (généré par un automate)
- TableAutomated (généré par un automate et implémentant tout le code rébarbatif représentant la structure des tables).
- Des fichier Table<NomDeTable> (ou nous implémentons ce que nous voulons)
A coté de notre projet
- Un petit programme automate
Bref, à vous d'imaginer. Mais quoi qu'il en soit, avec un tel modèle, on gagne un temps considérable, on préserve ses nerfs en évitant de code des lignes rébarbatives, on systématise sa démarche, on utilise aucune constante en dur même pas pour les requêtes SQL, on est réactif si il y a des changements sur la base, on est réactif si on doit adapter son modèle, on maitrise à 100% ce que fait l'automate, on peut faire évoluer son modèle au cours des années,...
ATTENTION : Je le répète, ce modèle n'est pas fonctionnel en l'état. C'est à vous d'imaginer le votre qui répondra à vos besoins. Il faut prendre le temps de la réflexion, mais après cela va très vite.
Je travail ainsi depuis des années, que ce soit dans le passé avec Delphi ou en Vb6 ou maintenant avec .Net. Et je peux vous garantir que travailler ainsi m'a plus d'une fois sauvé la vie et m'a fait gagner un temps considérable.
Pour ceux qui s'intérogent et se demandent pourquoi je n'ai pas présenté un modèle fonctionnel plutôt que le modèle ci dessus : 1) je n'aurai pas eu le temps de le faire 2) Un modèle fonctionnel serait trop complexe à présenter sur un forum et absolument pas pédagogique 3) La pluspart de les modèles actuels sont entièrement métier avec séparation DAL/BOL et les détailler dépasserait le propos 4) La majorité de mes modèles sont le fruit de plusieurs années de travail et d'évolution, et constituent une bonne part de mon gagne pain 5) Je ne peux pas présenter certains de ces modèles pour lesquels mes clients ont fait l'acquisition des droits sur les sources.
Partager