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
Shared
Function
GetPersonByPersonGuid
(
ByVal
PersonGuid As
Guid) As
PersonDTO
Dim
command As
SqlCommand =
GetDbSprocCommand
(
"Person_GetByPersonGuid"
)
command.Parameters.Add
(
CreateParameter
(
"@PersonGuid"
, PersonGuid))
Return
GetSingleDTO
(
Of
PersonDTO)(
command)
End
Function
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
Shared
ReadOnly
Property
ConnectionString
(
) As
String
Get
Return
ConfigurationManager.ConnectionStrings
(
"MyConnection"
).ConnectionString
End
Get
End
Property
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
Shared
Function
GetDbConnection
(
) As
SqlConnection
Return
New
SqlConnection
(
ConnectionString)
End
Function
' GetDbSqlCommand
Protected
Shared
Function
GetDbSQLCommand
(
ByVal
sqlQuery As
String
) As
SqlCommand
Dim
command As
SqlCommand =
New
SqlCommand
(
)
command.Connection
=
GetDbConnection
(
)
command.CommandType
=
CommandType.Text
command.CommandText
=
sqlQuery
Return
command
End
Function
' GetDbSprocCommand
Protected
Shared
Function
GetDbSprocCommand
(
ByVal
sprocName As
String
) As
SqlCommand
Dim
command As
SqlCommand =
New
SqlCommand
(
sprocName)
command.Connection
=
GetDbConnection
(
)
command.CommandType
=
CommandType.StoredProcedure
Return
command
End
Function
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
Shared
Function
CreateParameter
(
ByVal
name As
String
, ByVal
value As
Guid) As
SqlParameter
If
value.Equals
(
DTOBase.Guid_NullValue
) Then
' Si la valeur est null alors crée un paramètre null.
Return
CreateNullParameter
(
name, SqlDbType.UniqueIdentifier
)
Else
Dim
parameter As
SqlParameter =
New
SqlParameter
(
)
parameter.SqlDbType
=
SqlDbType.UniqueIdentifier
parameter.ParameterName
=
name
parameter.Value
=
value
parameter.Direction
=
ParameterDirection.Input
Return
parameter
End
If
End
Function
' CreateParameter - nvarchar
Protected
Shared
Function
CreateParameter
(
ByVal
name As
String
, ByVal
value As
String
, ByVal
size As
Integer
) As
SqlParameter
If
value =
DTOBase.String_NullValue
Then
' Si la valeur est null alors crée un paramètre null.
Return
CreateNullParameter
(
name, SqlDbType.NVarChar
)
Else
Dim
parameter As
SqlParameter =
New
SqlParameter
(
)
parameter.SqlDbType
=
SqlDbType.NVarChar
parameter.Size
=
size
parameter.ParameterName
=
name
parameter.Value
=
value
parameter.Direction
=
ParameterDirection.Input
Return
parameter
End
If
End
Function
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
Shared
Function
GetSingleDTO
(
Of
T As
DTOBase)(
ByRef
command As
SqlCommand) As
T
Dim
dto As
T =
Nothing
Try
command.Connection.Open
(
)
Dim
reader As
SqlDataReader =
command.ExecuteReader
(
)
If
reader.HasRows
Then
reader.Read
(
)
Dim
parser As
DTOParser =
DTOParserFactory.GetParser
(
GetType
(
T))
parser.PopulateOrdinals
(
reader)
dto =
DirectCast
(
parser.PopulateDTO
(
reader), T)
reader.Close
(
)
Else
' S'il n'y a pas de données, nous renvoyons null.
dto =
Nothing
End
If
Catch
e As
Exception
Throw
New
Exception
(
"Error populating data"
, e)
Finally
command.Connection.Close
(
)
command.Connection.Dispose
(
)
End
Try
' Renvoie le DTO, rempli soit avec des données soit avec null.
Return
dto
End
Function
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
MustInherit
Class
DALBase
' ConnectionString
Protected
Shared
ReadOnly
Property
ConnectionString
(
) As
String
Get
Return
ConfigurationManager.ConnectionStrings
(
"MyConn"
).ConnectionString
End
Get
End
Property
' GetDbSqlCommand
Protected
Shared
Function
GetDbSQLCommand
(
ByVal
sqlQuery As
String
) As
SqlCommand
Dim
command As
New
SqlCommand
(
)
command.Connection
=
GetDbConnection
(
)
command.CommandType
=
CommandType.Text
command.CommandText
=
sqlQuery
Return
command
End
Function
' GetDbConnection
Protected
Shared
Function
GetDbConnection
(
) As
SqlConnection
Return
New
SqlConnection
(
ConnectionString)
End
Function
' GetDbSprocCommand
Protected
Shared
Function
GetDbSprocCommand
(
ByVal
sprocName As
String
) As
SqlCommand
Dim
command As
SqlCommand =
New
SqlCommand
(
sprocName)
command.Connection
=
GetDbConnection
(
)
command.CommandType
=
CommandType.StoredProcedure
Return
command
End
Function
' CreateNullParameter
Protected
Shared
Function
CreateNullParameter
(
ByVal
name As
String
, ByVal
paramType As
SqlDbType) As
SqlParameter
Dim
parameter As
SqlParameter =
New
SqlParameter
(
)
parameter.SqlDbType
=
paramType
parameter.ParameterName
=
name
parameter.Value
=
Nothing
parameter.Direction
=
ParameterDirection.Input
Return
parameter
End
Function
' CreateNullParameter - avec la taille pour nvarchars
Protected
Shared
Function
CreateNullParameter
(
ByVal
name As
String
, ByVal
paramType As
SqlDbType, ByVal
size As
Integer
) As
SqlParameter
Dim
parameter As
SqlParameter =
New
SqlParameter
(
)
parameter.SqlDbType
=
paramType
parameter.ParameterName
=
name
parameter.Size
=
size
parameter.Value
=
Nothing
parameter.Direction
=
ParameterDirection.Input
Return
parameter
End
Function
' CreateOutputParameter
Protected
Shared
Function
CreateOutputParameter
(
ByVal
name As
String
, ByVal
paramType As
SqlDbType) As
SqlParameter
Dim
parameter As
SqlParameter =
New
SqlParameter
(
)
parameter.SqlDbType
=
paramType
parameter.ParameterName
=
name
parameter.Direction
=
ParameterDirection.Output
Return
parameter
End
Function
' CreateOuputParameter - avec la taille pour nvarchars
Protected
Shared
Function
CreateOutputParameter
(
ByVal
name As
String
, ByVal
paramType As
SqlDbType, ByVal
size As
Integer
) As
SqlParameter
Dim
parameter As
SqlParameter =
New
SqlParameter
(
)
parameter.SqlDbType
=
paramType
parameter.Size
=
size
parameter.ParameterName
=
name
parameter.Direction
=
ParameterDirection.Output
Return
parameter
End
Function
' CreateParameter - uniqueidentifier
Protected
Shared
Function
CreateParameter
(
ByVal
name As
String
, ByVal
value As
Guid) As
SqlParameter
If
value.Equals
(
DTOBase.Guid_NullValue
) Then
' Si la valeur est null alors crée un paramètre null.
Return
CreateNullParameter
(
name, SqlDbType.UniqueIdentifier
)
Else
Dim
parameter As
SqlParameter =
New
SqlParameter
(
)
parameter.SqlDbType
=
SqlDbType.UniqueIdentifier
parameter.ParameterName
=
name
parameter.Value
=
value
parameter.Direction
=
ParameterDirection.Input
Return
parameter
End
If
End
Function
' CreateParameter - int
Protected
Shared
Function
CreateParameter
(
ByVal
name As
String
, ByVal
value As
Integer
) As
SqlParameter
If
value =
DTOBase.Int_NullValue
Then
' Si la valeur est null alors crée un paramètre null.
Return
CreateNullParameter
(
name, SqlDbType.Int
)
Else
Dim
parameter As
SqlParameter =
New
SqlParameter
(
)
parameter.SqlDbType
=
SqlDbType.Int
parameter.ParameterName
=
name
parameter.Value
=
value
parameter.Direction
=
ParameterDirection.Input
Return
parameter
End
If
End
Function
' CreateParameter - datetime
Protected
Shared
Function
CreateParameter
(
ByVal
name As
String
, ByVal
value As
DateTime) As
SqlParameter
If
value =
DTOBase.DateTime_NullValue
Then
' Si la valeur est null alors crée un paramètre null.
Return
CreateNullParameter
(
name, SqlDbType.DateTime
)
Else
Dim
parameter As
SqlParameter =
New
SqlParameter
(
)
parameter.SqlDbType
=
SqlDbType.DateTime
parameter.ParameterName
=
name
parameter.Value
=
value
parameter.Direction
=
ParameterDirection.Input
Return
parameter
End
If
End
Function
' CreateParameter - nvarchar
Protected
Shared
Function
CreateParameter
(
ByVal
name As
String
, ByVal
value As
String
, ByVal
size As
Integer
) As
SqlParameter
If
value =
DTOBase.String_NullValue
Then
' Si la valeur est null alors crée un paramètre null.
Return
CreateNullParameter
(
name, SqlDbType.NVarChar
)
Else
Dim
parameter As
SqlParameter =
New
SqlParameter
(
)
parameter.SqlDbType
=
SqlDbType.NVarChar
parameter.Size
=
size
parameter.ParameterName
=
name
parameter.Value
=
value
parameter.Direction
=
ParameterDirection.Input
Return
parameter
End
If
End
Function
' GetSingleDTO
Protected
Shared
Function
GetSingleDTO
(
Of
T As
DTOBase)(
ByRef
command As
SqlCommand) As
T
Dim
dto As
T =
Nothing
Try
command.Connection.Open
(
)
Dim
reader As
SqlDataReader =
command.ExecuteReader
(
)
If
reader.HasRows
Then
reader.Read
(
)
Dim
parser As
DTOParser =
DTOParserFactory.GetParser
(
GetType
(
T))
parser.PopulateOrdinals
(
reader)
dto =
DirectCast
(
parser.PopulateDTO
(
reader), T)
reader.Close
(
)
Else
' S'il n'y a pas de données, nous renvoyons null.
dto =
Nothing
End
If
Catch
e As
Exception
Throw
New
Exception
(
"Error populating data"
, e)
Finally
command.Connection.Close
(
)
command.Connection.Dispose
(
)
End
Try
' Renvoie le DTO, rempli soit avec des données soit avec null.
Return
dto
End
Function
' GetDTOList
Protected
Shared
Function
GetDTOList
(
Of
T As
DTOBase)(
ByRef
command As
SqlCommand) As
List
(
Of
T)
Dim
dtoList As
New
List
(
Of
T)(
)
Try
command.Connection.Open
(
)
Dim
reader As
SqlDataReader =
command.ExecuteReader
(
)
If
reader.HasRows
Then
' Obtenir un analyseur (parser) pour ce type de DTO et remplir les ordinaux.
Dim
parser As
DTOParser =
DTOParserFactory.GetParser
(
GetType
(
T))
parser.PopulateOrdinals
(
reader)
' Utilise l'analyseur (parser) pour construire notre liste de DTO.
While
reader.Read
(
)
Dim
dto As
T =
Nothing
dto =
DirectCast
(
parser.PopulateDTO
(
reader), T)
dtoList.Add
(
dto)
End
While
reader.Close
(
)
Else
' S'il n'y a pas de données, nous renvoyons null.
dtoList =
Nothing
End
If
Catch
e As
Exception
Throw
New
Exception
(
"Error populating data"
, e)
Finally
command.Connection.Close
(
)
command.Connection.Dispose
(
)
End
Try
Return
dtoList
End
Function
End
Class
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