I. Introduction▲
Voici un schéma de la conception DAL que nous utilisons.
Dans la partie 1, nous avons parlé de l'architecture globale de la DAL et de l'utilisation de DTO pour déplacer des données dans et hors de la DAL. Nous avons également traité la classe PersonDb. Cette fois, nous allons regarder la classe DALBase et comment écrire le code pour construire les classes PersonDb et d'autres classes EntityDb.
II. La classe DALBase▲
Notre classe PersonDb contient l'ensemble de nos méthodes d'accès aux données pour obtenir et définir les données pour une entité personne. La dernière fois, nous écrivions des méthodes PersonDb qui ressemblaient à :
// GetPersonByPersonGuid
public
static
PersonDTO GetPersonByPersonGuid
(
Guid PersonGuid)
{
SqlCommand command =
GetDbSprocCommand
(
"Person_GetByPersonGuid"
);
command.
Parameters.
Add
(
CreateParameter
(
"@PersonGuid"
,
PersonGuid));
return
GetSingleDTO<
PersonDTO>(
ref
command);
}
Maintenant, nous devons écrire les méthodes de la classe DALBase nécessaires pour faire ce travail. Nous allons avoir besoin d'une méthode GetDbSprocCommand(). Certains CreateParameter() surchargés pour chaque type de paramètre que nous devons créer. Et, le plus important, nous aurons besoin de méthodes génériques GetSingleDTO()et GetDTOList() qui recevront un objet Command et qui transformeront (parse) ensuite les données en un seul DTO ou en une liste générique de DTO. Je vais passer en revue chaque partie de la classe DALBase, puis à la fin de ce document, je reprendrai le code complet de la classe.
III. Chaîne de connexion▲
Premièrement, nous devons gérer la chaîne de connexion. Nous allons créer une propriété pour encapsuler la chaîne de connexion du ConfigurationManager.
// ConnectionString
protected
static
string
ConnectionString
{
get
{
return
ConfigurationManager.
ConnectionStrings[
"MyConnection"
].
ConnectionString;
}
}
IV. SqlConnection et des objets de commande ▲
Maintenant, nous allons créer des méthodes pour obtenir une instance de SQLConnection, une instance de SqlCommand pour le TSQL, et puisque nous allons utiliser des procédures stockées, une instance de SqlCommand pour une procédure stockée. Notez que GetDbSprocCommand() est la méthode que nous utilisons dans notre code PersonDb. Maintenant que nous l'écrivons, nous savons exactement les paramètres dont elle a besoin ainsi que son type de retour.
// GetDbConnection
protected
static
SqlConnection GetDbConnection
(
)
{
return
new
SqlConnection
(
ConnectionString);
}
// GetDbSqlCommand
protected
static
SqlCommand GetDbSQLCommand
(
string
sqlQuery)
{
SqlCommand command =
new
SqlCommand
(
);
command.
Connection =
GetDbConnection
(
);
command.
CommandType =
CommandType.
Text;
command.
CommandText =
sqlQuery;
return
command;
}
// GetDbSprocCommand
protected
static
SqlCommand GetDbSprocCommand
(
string
sprocName)
{
SqlCommand command =
new
SqlCommand
(
sprocName);
command.
Connection =
GetDbConnection
(
);
command.
CommandType =
CommandType.
StoredProcedure;
return
command;
}
V. Création des SqlParameters ▲
Maintenant que nous avons une méthode pour créer une instance de SQLCommand pour procédure stockée, nous devons être en mesure de créer des paramètres. Lorsque nous avons rédigé notre code de la classe PersonDb, nous avons fixé un objectif pour la façon dont nous voulons que le CreateParameter() fonctionne. Nous voulons une méthode CreateParameter() unique qui peut être utilisée pour ajouter des paramètres integer, guid, string, datetime, et nous voulons qu'elle ait une syntaxe simple qui fonctionne sur une seule ligne, comme indiqué ci-dessous.
command.
Parameters.
Add (
CreateParameter (
"@Email"
,
email,
100
));
Cela devrait être possible. Notre méthode CreateParameter() prendra au moins deux paramètres. Le premier sera le nom du SqlParameter qui sera toujours une chaîne. Le deuxième sera la valeur du SqlParameter et le type de ce paramètre nous dit quel type de SqlParameter nous devrons créer. Donc, si nous avons une signature de méthode qui ressemble à CreateParameter(string name, guid value), nous savons que nous devons retourner un SqlParameter de Type SqlDbType.UniqueIdentifier. Si nous avons une signature de méthode qui ressemble à CreateParameter(string name, int value), nous savons que nous devons retourner un paramètre de type SqlDbType.Int. Voici à quoi nos méthodes CreateParameter() surchargées vont ressembler. Remarquez que nous avons ajouté un paramètre « size » supplémentaire à la signature de la surcharge qui crée un SqlParameter NvarChar.
// CreateParameter - uniqueidentifier
protected
static
SqlParameter CreateParameter
(
string
name,
Guid value
)
{
if
(
value
.
Equals
(
Common.
DTOBase.
Guid_NullValue))
{
return
CreateNullParameter
(
name,
SqlDbType.
UniqueIdentifier);
}
else
{
SqlParameter parameter =
new
SqlParameter
(
);
parameter.
SqlDbType =
SqlDbType.
UniqueIdentifier;
parameter.
ParameterName =
name;
parameter.
Value =
value
;
parameter.
Direction =
ParameterDirection.
Input;
return
parameter;
}
}
// CreateParameter - nvarchar
protected
static
SqlParameter CreateParameter
(
string
name,
string
value
,
int
size)
{
if
(
value
==
Common.
DTOBase.
String_NullValue)
{
return
CreateNullParameter
(
name,
SqlDbType.
NVarChar);
}
else
{
SqlParameter parameter =
new
SqlParameter
(
);
parameter.
SqlDbType =
SqlDbType.
NVarChar;
parameter.
Size =
size;
parameter.
ParameterName =
name;
parameter.
Value =
value
;
parameter.
Direction =
ParameterDirection.
Input;
return
parameter;
}
}
Donc, nous faisons une méthode comme celles ci-dessus pour chaque type de SqlParameter que nous devons créer (datetime, int, etc.). Nous avons également besoin d'avoir un moyen de créer des SqlParameters avec une valeur null, et des paramètres de sortie. La meilleure façon que j'aie trouvée pour le faire est d'utiliser les techniques ci-dessus pour réaliser une méthode CreateOutputParameter() supplémentaire et une méthode CreateNullParameter(). Le code correspondant peut être trouvé dans le listing complet de la classe à la fin de ce document.
VI. GetSingleDTO() ▲
Nous arrivons à quelque chose d'un peu plus intéressant. Nous avons codé les méthodes prédéfinies nécessaires pour créer un objet de commande et ajouter les paramètres associés.
Maintenant, nous devons prendre cet objet de commande et obtenir de lui un ensemble utilisable de données, notre PersonDTO. C'est le but de GetSingleDTO() qui prend un objet de commande, ouvre sa connexion, appelle ExecuteReader() sur cette dernière, tente d'obtenir un seul DTO du reader (lecteur), puis le renvoie. Puisque nous ne voulons pas de réécriture de code pour chaque type de DTO, nous avons fait une méthode générique GetSingleDTO qui accepte n'importe quel type qui hérite de DTOBase. Vous remarquerez que GetSingleDTO() encapsule la logique qui serait autrement répétée dans chaque méthode d'accès aux données que nous écrivons. Des opérations comme l'ouverture de la connexion, l'obtention d'un reader ainsi que la vérification de la présence de lignes dans le reader sont des fonctionnalités que nous faisons de la même façon à chaque fois que nous avons besoin d'obtenir des données. Alors, pourquoi écrire encore et encore ? Nous pouvons simplement tout encapsuler ici dans notre méthode GetSingleDTO(). En fait, nous n'avons pas vraiment le choix. Nous avons tout encapsulé ici parce que nous avons déjà écrit notre code d'accès aux données PersonDb et il exige que GetSingleDTO() prenne un objet de commande comme paramètre et renvoie un DTO du type demandé. Nous avons codé notre méthode pour cette façon de procéder.
// GetSingleDTO
protected
static
T GetSingleDTO<
T>(
ref
SqlCommand command) where
T :
DTOBase
{
T dto =
null
;
try
{
command.
Connection.
Open
(
);
SqlDataReader reader =
command.
ExecuteReader
(
);
if
(
reader.
HasRows)
{
reader.
Read
(
);
DTOParser parser =
DTOParserFactory.
GetParser
(
typeof
(
T));
parser.
PopulateOrdinals
(
reader);
dto =
(
T)parser.
PopulateDTO
(
reader);
reader.
Close
(
);
}
else
{
// S'il n'y a pas de données, nous renvoyons null.
dto =
null
;
}
}
catch
(
Exception e)
{
throw
new
Exception
(
"Error populating data"
,
e);
}
finally
{
command.
Connection.
Close
(
);
command.
Connection.
Dispose
(
);
}
// Renvoie le DTO, rempli soit avec des données soit avec null.
return
dto;
}
Cette méthodologie prend place après que nous confirmions que le reader a vraiment des lignes (HasRow). À ce moment-là, nous appelons Read() et créons un DTOParser pour le type que nous voulons renvoyer. Les deux prochaines étapes sont la clé. D'abord, nous appelons parser.PopulateOrdinals(). Cette méthode reçoit et affecte les ordinaux pour chaque champ de données que nous devons extraire du Reader. Un ordinal est essentiellement un indice qui vous indique où trouver une valeur de champ de données spécifique dans le reader. Obtenir des valeurs par ordinal (index) est beaucoup plus efficace que d'obtenir des valeurs par le nom de champ (colonne) de données. Après avoir renseigné les ordinaux, nous sommes alors en mesure d'utiliser le Parser (analyseur/répartiteur) pour remplir l'objet DTO que notre méthode renverra.
Vous remarquerez que le code ci-dessus ne contient aucune logique pour parser le DTO en cours depuis le reader. C'est parce que les changements logiques dépendent du type de DTO que nous recevons. Donc, nous encapsulons ce qui change dans un objet DTOParser. Nous allons alors simplement utiliser le DTOParser pour obtenir le type de DTO que nous recherchons et nous laissons le DTOParser se soucier de la façon dont le parsing (analyse/répartition) est réellement fait.
La méthode GetDTOList() utilise le même design que ci-dessus, elle retourne simplement une liste générique de DTO lieu d'un seul DTO. La partie de code de GetDTOList() est incluse dans la classe complète située à la fin de ce document.
VII. Résumé ▲
Maintenant, nous avons couvert la classe DALBase. Dans le prochain article, nous allons entrer dans la vraie magie de la performance où nous prendrons ce reader, lequel sera passé au DTOParser, et utilisera les données ordinales ainsi que les méthodes (Get) fortement typées pour extraire des données de la manière la plus efficace possible sans caster l'objet.
Voici le listing complet de DALBase.
public
abstract
class
DALBase
{
// ConnectionString
protected
static
string
ConnectionString
{
get
{
return
ConfigurationManager.
ConnectionStrings[
"MyConn"
].
ConnectionString;
}
}
// GetDbSqlCommand
protected
static
SqlCommand GetDbSQLCommand
(
string
sqlQuery)
{
SqlCommand command =
new
SqlCommand
(
);
command.
Connection =
GetDbConnection
(
);
command.
CommandType =
CommandType.
Text;
command.
CommandText =
sqlQuery;
return
command;
}
// GetDbConnection
protected
static
SqlConnection GetDbConnection
(
)
{
return
new
SqlConnection
(
ConnectionString);
}
// GetDbSprocCommand
protected
static
SqlCommand GetDbSprocCommand
(
string
sprocName)
{
SqlCommand command =
new
SqlCommand
(
sprocName);
command.
Connection =
GetDbConnection
(
);
command.
CommandType =
CommandType.
StoredProcedure;
return
command;
}
// CreateNullParameter
protected
static
SqlParameter CreateNullParameter
(
string
name,
SqlDbType paramType)
{
SqlParameter parameter =
new
SqlParameter
(
);
parameter.
SqlDbType =
paramType;
parameter.
ParameterName =
name;
parameter.
Value =
null
;
parameter.
Direction =
ParameterDirection.
Input;
return
parameter;
}
// CreateNullParameter - avec la taille pour nvarchars
protected
static
SqlParameter CreateNullParameter
(
string
name,
SqlDbType paramType,
int
size)
{
SqlParameter parameter =
new
SqlParameter
(
);
parameter.
SqlDbType =
paramType;
parameter.
ParameterName =
name;
parameter.
Size =
size;
parameter.
Value =
null
;
parameter.
Direction =
ParameterDirection.
Input;
return
parameter;
}
// CreateOutputParameter
protected
static
SqlParameter CreateOutputParameter
(
string
name,
SqlDbType paramType)
{
SqlParameter parameter =
new
SqlParameter
(
);
parameter.
SqlDbType =
paramType;
parameter.
ParameterName =
name;
parameter.
Direction =
ParameterDirection.
Output;
return
parameter;
}
// CreateOuputParameter - avec la taille pour nvarchars
protected
static
SqlParameter CreateOutputParameter
(
string
name,
SqlDbType paramType,
int
size)
{
SqlParameter parameter =
new
SqlParameter
(
);
parameter.
SqlDbType =
paramType;
parameter.
Size =
size;
parameter.
ParameterName =
name;
parameter.
Direction =
ParameterDirection.
Output;
return
parameter;
}
// CreateParameter - uniqueidentifier
protected
static
SqlParameter CreateParameter
(
string
name,
Guid value
)
{
if
(
value
.
Equals
(
Common.
DTOBase.
Guid_NullValue))
{
// Si la valeur est null alors crée un paramètre null.
return
CreateNullParameter
(
name,
SqlDbType.
UniqueIdentifier);
}
else
{
SqlParameter parameter =
new
SqlParameter
(
);
parameter.
SqlDbType =
SqlDbType.
UniqueIdentifier;
parameter.
ParameterName =
name;
parameter.
Value =
value
;
parameter.
Direction =
ParameterDirection.
Input;
return
parameter;
}
}
// CreateParameter - int
protected
static
SqlParameter CreateParameter
(
string
name,
int
value
)
{
if
(
value
==
Common.
DTOBase.
Int_NullValue)
{
// Si la valeur est null alors crée un paramètre null.
return
CreateNullParameter
(
name,
SqlDbType.
Int);
}
else
{
SqlParameter parameter =
new
SqlParameter
(
);
parameter.
SqlDbType =
SqlDbType.
Int;
parameter.
ParameterName =
name;
parameter.
Value =
value
;
parameter.
Direction =
ParameterDirection.
Input;
return
parameter;
}
}
// CreateParameter - datetime
protected
static
SqlParameter CreateParameter
(
string
name,
DateTime value
)
{
if
(
value
==
Common.
DTOBase.
DateTime_NullValue)
{
// Si la valeur est null alors crée un paramètre null.
return
CreateNullParameter
(
name,
SqlDbType.
DateTime);
}
else
{
SqlParameter parameter =
new
SqlParameter
(
);
parameter.
SqlDbType =
SqlDbType.
DateTime;
parameter.
ParameterName =
name;
parameter.
Value =
value
;
parameter.
Direction =
ParameterDirection.
Input;
return
parameter;
}
}
// CreateParameter - nvarchar
protected
static
SqlParameter CreateParameter
(
string
name,
string
value
,
int
size)
{
if
(
value
==
Common.
DTOBase.
String_NullValue)
{
// Si la valeur est null alors crée un paramètre null.
return
CreateNullParameter
(
name,
SqlDbType.
NVarChar);
}
else
{
SqlParameter parameter =
new
SqlParameter
(
);
parameter.
SqlDbType =
SqlDbType.
NVarChar;
parameter.
Size =
size;
parameter.
ParameterName =
name;
parameter.
Value =
value
;
parameter.
Direction =
ParameterDirection.
Input;
return
parameter;
}
}
// GetSingleDTO
protected
static
T GetSingleDTO<
T>(
ref
SqlCommand command) where
T :
DTOBase
{
T dto =
null
;
try
{
command.
Connection.
Open
(
);
SqlDataReader reader =
command.
ExecuteReader
(
);
if
(
reader.
HasRows)
{
reader.
Read
(
);
DTOParser parser =
DTOParserFactory.
GetParser
(
typeof
(
T));
parser.
PopulateOrdinals
(
reader);
dto =
(
T)parser.
PopulateDTO
(
reader);
reader.
Close
(
);
}
else
{
// S'il n'y a pas de données, nous renvoyons null.
dto =
null
;
}
}
catch
(
Exception e)
{
throw
new
Exception
(
"Error populating data"
,
e);
}
finally
{
command.
Connection.
Close
(
);
command.
Connection.
Dispose
(
);
}
// Renvoie le DTO, rempli soit avec des données soit avec null.
return
dto;
}
// GetDTOList
protected
static
List<
T>
GetDTOList<
T>(
ref
SqlCommand command) where
T :
DTOBase
{
List<
T>
dtoList =
new
List<
T>(
);
try
{
command.
Connection.
Open
(
);
SqlDataReader reader =
command.
ExecuteReader
(
);
if
(
reader.
HasRows)
{
// Obtenir un analyseur (parser) pour ce type de DTO et remplir les ordinaux.
DTOParser parser =
DTOParserFactory.
GetParser
(
typeof
(
T));
parser.
PopulateOrdinals
(
reader);
// Utilise l'analyseur (parser) pour construire notre liste de DTO.
while
(
reader.
Read
(
))
{
T dto =
null
;
dto =
(
T)parser.
PopulateDTO
(
reader);
dtoList.
Add
(
dto);
}
reader.
Close
(
);
}
else
{
// S'il n'y a pas de données, nous renvoyons null.
dtoList =
null
;
}
}
catch
(
Exception e)
{
throw
new
Exception
(
"Error populating data"
,
e);
}
finally
{
command.
Connection.
Close
(
);
command.
Connection.
Dispose
(
);
}
return
dtoList;
}
}
VIII. Remerciements▲
Je remercie M. Lacovara de m'avoir permis de traduire sa série d'articles « High Performance Data Access Layer Architecture ».
Gaëtan Wauthy et Kropernic pour la relecture et la validation technique, ainsi qu'une première relecture orthographique.
Claude Leloup pour la relecture orthographique.
IX. Source▲
Traduction de l'article de M. Rudy Lacovara — High Performance Data Access Layer Architecture Part 2