Architecture de couche d'accès aux données (DAL) de hautes performances — Partie 2

DTO : Data Transfert Object (Objet de transfert de données)

Rudy Lacovara

Traducteur

Hervé Taraveau

Cette série de trois articles décrit comment écrire une couche d'accès aux données de hautes performances (DAL).

(Exemple de code en VB.NET)

Partie 2

Mise en œuvre de l'implémentation de la classe DALBase.

Étude de l'écriture des méthodes qui sont utilisées dans l'implémentation des classes PersonDb et autres classes EntityDb.

Commentez cet article : Commentez

- Titre : Architecture de couche d'accès aux données (DAL) de hautes performances — Partie 2

- Auteur : Rudy Lacovara

- Traducteur : Hervé Taraveau

- Parution : 11 décembre 2013

- Public visé :

- Licence : Copyright ® 2013 Hervé Taraveau. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- II. La classe DALBase

- III. Chaîne de connexion

- IV. SqlConnection et des objets de commande

- V. Création des SqlParameters

- VI. GetSingleDTO()

- VII. Résumé

- VIII. Remerciements

- IX. Source

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

' 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

' 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

' 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

' 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

' 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.

La classe 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

Partie 1

Partie 2 — [haut]

Partie 3