Après 4 mois d'abstinence, je reviens avec un post plutôt simple, voir très simple, mais qui va servir d'introduction à une série plus longue sur du code que j'ai recement écrit pour mon client unique et favoris. J'ai donc eu l'occasion de mettre en place un un mini framework. Pour la petite histoire, mes articles précédents font partie de ce framework, à savoir principalement : les singletons (sujet qui fait grand débat), mon propre cache et la configuration (dont l'explication reste inachevée mais je n'avais pas l'impression que cela captivait énormément les rares personnes qui venaient se perdre sur ce blog).
Mais revenons à nos moutons ; une des fonctions de ce mini framework est de gérer les entités, leurs chargement depuis la base de données, et le mapping entre ces entitées et le DataReader résultant de l'exécution d'une procédure stockée. Dans le service auquel j'appartiens, la version actuelle du code faisait un mapping écrit à la main par les developpeurs :
1: monObjet.MaPropriété1 = (MonType)dataReader["MaColonne1"];
2: monObjet.MaPropriété2 = (MonType1)dataReader["MaColonne2"];
3: monObjet.MaPropriété3 = (MonType2)dataReader["MaColonne3"];
4: monObjet.MaPropriété4 = dataReader["MaColonne4"] as MonType4;
5: monObjet.MaPropriété5 = (MonType5)dataReader["MaColonne5"];
Etant un gros feignant, je voulais quelque chose de moins fastidieux, plus sûr, mais qui ne pénalise pas non plus la performance. Qui plus est, j'avais en tête un système permettant de ne loader que partiellement les objets, il me fallait donc que le code précédent prenne aussi en compte la possibilité que la colonne ne soit pas présente dans le DataReader (par exemple je veux une liste de personne pour afficher leur nom et prénom, cela ne sert donc à rien de récupérer leur sexe ou date de naissance). La génération avec des outils tels que CodeSmith aurait certainement pu faire l'affaire mais je n'en suis pas un grand fan, et c'est aussi beaucoup moins marrant que de coder. C'est pourquoi je me suis mis à l'écrire d'un mapper.
Je me suis fortement inspiré du code de Michel Perfetti qui avait fait un peu la même chose. La différence entre son code et le mien, c'est que Michel se basait sur l'index des colonnes pour effectuer son mapping, or cela n'était pas possible dans mon cas, puisque cet ordre n'était pas prédéfini à l'avance, le nombre de colonnes changeant à chaque requête.
J'ai donc commencer par le commencement, créer mon propre attribut de mapping qui me servira à faire le lien entre ma variable de classe ou bien une propriétée de ma classe et la colonne de mon DataReader. Cet attribut devait avoir comme propriétés exposées :
- ColumnName : le nom de la colonne dans le DataReader
- BitFieldIndex : une puissance de 2 permettant de savoir le membre est chargé (pour le cas d'un chargement partiel)
Pour résumé à quoi sert ce BitFieldIndex, chaque champs possède une puissance de 2 comme index : 1, 2, 4, 8,16... Au sein de l'entité, une variable permet de savoir quels champs sont chargés ou non grâce à une combinaison de ces puissances. Par exemple si cette variable vaut 11, alors cela signifie que les champs d'index 1, 2 et 8 sont chargés. L'appel au un champ d'index 4 déclenchera dans ce cas un reload complet de l'objet.
Sans plus attendre et pour conclure, le code au combien compliqué de cet attribut suivi de son utilisation dans une autre classe :
1: [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
2: public class MappingAttribute : Attribute
3: {
4: private string _columnName;
5: private ulong _bitFieldIndex;
6: public MappingAttribute()
7: {
8: }
9: public string ColumnName
10: {
11: get { return _columnName; }
12: set { _columnName = value; }
13: }
14: public ulong BitFieldIndex
15: {
16: get { return _bitFieldIndex; }
17: set { _bitFieldIndex = value; }
18: }
19: }
20:
1: public class News : Entity<int>
2: {
3: #region Mapped Fields
4:
5: private enum MF
6: {
7: Id = 1,
8: Name = 2,
9: Content = 4,
10: CreationDate = 8,
11: ModificationDate = 16
12: }
13:
14: #endregion
15:
16: #region Private Members
17:
18: [MappingAttribute(ColumnName = "NewsId", BitFieldIndex = (ulong)MF.Id)]
19: private int _id;
20:
21: [MappingAttribute(ColumnName = "Name", BitFieldIndex = (ulong)MF.Name)]
22: private string _name;
23:
24: [MappingAttribute(ColumnName = "Content", BitFieldIndex = (ulong)MF.Content)]
25: private string _content;
26:
27: [MappingAttribute(ColumnName = "CreationDate", BitFieldIndex = (ulong)MF.CreationDate)]
28: private DateTime _creationDate;
29:
30: [MappingAttribute(ColumnName = "ModificationDate", BitFieldIndex = (ulong)MF.ModificationDate)]
31: private DateTime _modificationDate;
32:
33: #endregion
34:
35: }
36:
Petite dédicace à bibi : Ne pars pas !
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :