IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

Architecture de couche d'accès aux données (DAL) de hautes performances - Partie 1
Un tutoriel de Rudy Lacovara, traduit par Hervé Taraveau

Le , par rv26t

0PARTAGES

7  0 
bonjour,

Cette discussion est destinée à recueillir vos commentaires sur l'article « Architecture de couche d'accès aux données (DAL) de hautes performances — Partie 1 » et des Data Ttransfert Object (DTO).
Traduction de l'article « High Performance Data Access Layer Architecture Part 1 » de M. Rudy Lacovara.

DTO, DAL, BLL… Tout le monde s'en sort ? Pas si sûr !
Voici le premier chapitre d'un article qui vous aidera à comprendre cette architecture et ses concepts-clés.
Dans cette première partie, nous allons nous intéresser à l'architecture globale de la DAL et l'utilisation des DTO pour transférer des données entre les différentes couches de l'application.
Nous allons aussi voir la mise en pratique de ces concepts à l'aide d'une classe PersonDB qui contiendra l'ensemble de nos méthodes d'accès aux données permettant d'obtenir et de sauvegarder les données d'une entité « personne ».
Cette série de trois articles décrit comment écrire une couche d'accès aux données de hautes performances.
Lien sur les discussions des autres articles : Discussion sur la partie 2, Discussion sur la partie 3

Bonne lecture.

Une erreur dans cette actualité ? Signalez-nous-la !

Avatar de Skalp
Rédacteur/Modérateur https://www.developpez.com
Le 05/01/2014 à 17:34
Il me semble que cette discussion a divergé du sujet principal de cette série d'articles.
Comme l'a dit tomlev, le passage par référence ou par valeur est un détail dont l'effet sur les performances est largement négligeable par rapport à ce que ces articles nous montrent.
De plus, le test EF mentionné en lien de l'article traite de la version 3 beta et non de la première version.
Ceci dit, EF a bien évolué depuis et pas mal de choses sont possibles (notamment le mode Code First) en plus des améliorations de performances.
Mais pour moi, ce qui pêche avec les ORM, c'est le mode "boîte noire". Quand tu n'es pas un expert du framework, tu ne sais pas ce qu'il se passe sous le capot et tu n'as pas la main pour faire comme tu veux.

Cette série d'articles a un grand intérêt éducatif car la couche d'accès aux données est réalisée entièrement avec ADO.Net, qui tend a être oublié par les plus jeunes développeurs au profit des ORM. D'autre part, l'implémentation, bien que discutable, présente l'intérêt d'être "au goût du jour" (utilisation de DTO et non de DataSet, entre autres), contrairement aux exemples de code de la MSDN.
4  0 
Avatar de meziantou
Membre émérite https://www.developpez.com
Le 04/04/2014 à 1:11
A mon humble avis EF Code-First est une hérésie pour faire plaisir à certains. En effet cela ne fait pas gagner de temps de dev et à mon avis permet uniquement au gens ne maitrisant pas SQL de faire des d’application. Mais peut-on vraiment faire une application sans connaitre SQL ? Je pense même au final que l’on perd du temps à cause de l’effet boite noire que tu décris, des différents problèmes/limitations auxquels on peut être confrontés (il suffit de regarder le nombre important de questions sur ce forum ou d’autres) et du temps de monté en compétence sur la techno afin de bien l’utiliser.

L’approche Model-First est déjà plus intéressante puisqu’elle permet de générer une quantité plus ou moins importante de code à partir d’un modèle (même si celui-ci est finalement assez pauvre, mais après tout EF n’est qu’un ORM). Si vous partez sur cette approche je vous conseille de regarder ce que propose Matthieu Mezil.

Les DTO sont également un truc dont je ne comprends pas l’intérêt, en tout cas tels qu’ils sont utilisés dans de nombreux projets. La programmation orientée objet indique qu’un objet contient des données (propriétés) et des comportements (méthodes). A force de faire des DTO, des classes pour la validation, des repository, des factory, etc., et de vouloir suivre "les bonnes pratiques" en faisant des interfaces sans aucun sens dans l’unique but de faire de l’IOC et de pouvoir mettre des petits bouchons (stub) un peu partout, on se retrouve vite avec des modèles anémiques et difficilement maintenable. (J'extrapôle un peu par rapport à vos dire mais malheureusement on voit cela tellement souvent)

Ecrire un T4 pour générer du code aussi important que celui-ci (il s’agit tout de même des fondations de l’application) n’est pas aisé. Il y a de nombreux cas à gérer et la combinatoire est très compliquée sauf si le code que l’on génère ne fait presque rien (mais les projets évolue et on souhaite toujours générer plus de code). De plus on se retrouve rapidement à le modifier pour l’adapter à chaque projet. Par exemple si je souhaite tracer l’appel aux méthodes ou ajouter un mécanisme de cache (ou les 2 en même temps) il faut créer plusieurs version du template et bien évidemment les maintenir. On se retrouve donc à passer beaucoup de temps sur l’écriture de ces Templates et donc moins sur l’application en elle-même (et ce à chaque projet).

Une approche basée sur CodeDom ou toute autre représentation en mémoire du code est beaucoup plus intéressante puisqu’elle permet de générer le code « de base » puis d’y ajouter des comportements de manière unitaire. Par exemple pour ajouter des traces il suffit de parcourir la représentation du code et de la modifier. Une fois tous les traitements effectués on génère les fichiers. De plus on peut patcher la génération du code de base sans impacter les « extensions », la maintenance est donc simplifiée. Par contre le coût initial pour mettre en place la première solution est plus important.

Les ORM génère du code dynamiquement. Je ne dis pas que cela est mal, mais je pense que les procédures stockées ont encore de nombreux avantages :
- Modifiable sans recompiler l’application
- Un DBA peut faire son taf plus facilement puisqu’il a toutes les cartes en main
- Abstraction de la structure de la base de données pour l’application
- Permission plus fine pour l’application
- Totalement prédictible sans lancer l’application
Parfois on n’a pas le choix car le SGBD ne supporte pas les procs stock

D’un autre côté le développeur n’a pas envie d’écrire de la plomberie (même si ça occupe les développeurs des sociétés de service…). Personnellement je préfère coder des choses où il y a une vraie valeur.
Doit-on forcément tout faire à la main ou utiliser un ORM ? La réponse est non. En .NET il y a par exemple CodeFluent Entities, un générateur de code basé avec une approche Model-First, qui utilise des proc stock et ADO.NET et dont tout le code est statique/prédictible (aucune requête n’est généré dynamiquement à l’exécution).

Tu peux également regarder les ORM lite, comme Dapper, qui traitent le strict minimum de choses, mais qui le font bien et simplement. Tomlev a écrit un article sur Dapper si tu souhaites plus d'information http://tlevesque.developpez.com/tuto...s-avec-dapper/

Comme toujours chaque solution à ses avantages et ses inconvénients. A toi de peser le pour et le contre pour ton besoin et de faire un choix.
4  0 
Avatar de tomlev
Rédacteur/Modérateur https://www.developpez.com
Le 22/11/2013 à 20:22
Citation Envoyé par tomlev Voir le message
Ça ne sert effectivement strictement à rien de passer un type référence par référence, sauf bien sûr si la méthode affecte un autre objet au paramètre (ce qui n'est pas le cas ici).
Bon, j'ai peut-être parlé un peu vite... j'ai fait un petit benchmark, et il y a effectivement une différence de performance. Dans certains cas, le passage par référence est légèrement plus rapide (de 5 à 10%), mais j'ai aussi vu des cas ou c'est au contraire un peu plus lent. Mais je n'ai pas bien réussi à cerner ce qui fait que c'est plus rapide ou plus lent...

Le code IL généré est un peu plus long pour une méthode qui prend des paramètres par référence, à cause de l'indirection supplémentaire, donc il me semble que ça devrait toujours être plus lent... c'est peut-être à cause du fait qu'il y a une variable de moins à allouer sur la pile.

Bref, dans un cas comme ça où l'optimisation des performances est le but, ça peut peut-être se justifier ; mais à mon avis le gain de performance lié au passage par référence est insignifiant par rapport au temps pour exécuter une requête sur la DB...
3  0 
Avatar de rv26t
Modérateur https://www.developpez.com
Le 22/11/2013 à 18:56
Citation Envoyé par anthyme Voir le message
...
Ensuite cet article a quasiment 5 ans et traite donc de la première version d'entity framework qui, comme tout le monde le sait, avait ses défauts de jeunesse. Pour moi cet article a une pertinence assez relative aujourd'hui et ne sert pas dans la majorité des cas mais seulement lorsque l'on a des modèles de base de données vraiment complexe.

Cette phrase :
ADO.Net Entity Framework peut être de 50 % à 300 % plus lent que ADO.Net utilisant les ordinaux et SqlDataReaders.
Ne veut pas dire grand chose et même si elle était vrai à l'époque (ce dont je doute) le serait bien moins aujourd'hui.
Avec l'utilisation des pocos, l'option AsNoTracking, la compilation des requêtes linq et en utilisant pas les includes sur les relations 1-n on arrive au mêmes résultats dans des performances similaires qu'un ado.net basique.
Et avec l'arrivé de nouvelles mécaniques comme le cache de 2éme niveau on peut avoir des performances supérieures sans effort.

L'autre intérêt d'entity framework c'est que vous n'êtes pas obligé de l'utiliser partout.
Vous voulez faire de l'insert de masse ? Vous pouvez faire un bulk insert.
Une requête trop complexe pour être fait en linq ? Fait une proc stock dédié qui peut même retourner vos entités.

Bref n'oubliez par une des règles fondamentales en développement : "Ne pas optimiser prématurément" et au contraire garder un code simple plus facilement optimisable et refactorable plus tard sur les vrais points de contention de performance (optimiser les 20% du code qui mange les 80% de performances).

En multipliant par 100 le nombre de lignes de code de la DAL (par rapport a EF) au delà du temps de développement, on diminue notre capacité a la modifier, optimiser et la maintenir une application ...
Le but n'est pas de faire le procès des ORM. L'utilisation d'Entity Framework est certainement pratique, mais il existe des cas de figure ou l'on souhaite procéder différemment. (D'ailleurs vous soulignez cette possibilité avec E.F.)
Dans l'article que j'ai traduit, j'ai trouvé intéressant la partie qui traite des ordinaux, utile lors de traitement de gros volume de données.
Note personnelle : le problème de réécriture du code, c'est ce qui m'a poussé à rechercher à simplifier (tout basculer sur autre chose n'est pas forcément possible), mais je n'ai que partiellement résolu mon problème.

Je ne suis pas le seul à chercher dans ce sens.
Dans un de mes posts précédant (N°6), j'ai indiqué un tuto sur une autre approche (discrètement et en petit c'est vrai).
Il s'agit de Accès aux données avec Dapper. (Je l'ai découvert très récemment — article paru le 20 octobre 2013.) La critique par rapport aux ORM reste équivalente, mais souligne aussi le problème de productivité lié à l'écriture du code.
On peut constater avec Dapper que le code se trouve simplifié que se soit au niveau de la Lecture des résultats en tant qu'entités ou de la gestion des Requêtes paramétrées

Attention, je ne dit pas qu'il est préférable de l'utiliser par rapport à E.F. ou d'autres ORM, mais savoir que cela existe peut s'avérer utile dans certains cas. (Avoir le choix ou connaître d'autres approches)
2  0 
Avatar de meziantou
Membre émérite https://www.developpez.com
Le 18/11/2013 à 22:07
J'ai quelques remarques:
- Pourquoi passer les arguments en ref :
Code : Sélectionner tout
protected static T GetSingleDTO<T>(ref SqlCommand command) where T : DTOBase
- Pourquoi utiliser SqlConnection, SqlDataReader, etc. au lieu de IDbConnection, IDataReader, etc.

Code : Sélectionner tout
if (reader.HasRows) { reader.Read();
Pourquoi ne pas directement faire
Code : Sélectionner tout
if(reader.Read())
2  1 
Avatar de rv26t
Modérateur https://www.developpez.com
Le 19/11/2013 à 2:00
Bonjour meziantou

Merci pour tes remarques. (concerne la partie 2)

Avant tout, je préfère rappeler qu'il s'agit d'une traduction que j'ai faite, et je ne connais donc pas forcement ce que pense l'auteur (M. Lacovara) et pourquoi il a codé ainsi.
Mais nous pouvons bien sur réfléchir sur ce qu'il a fait et sur d'autres approches différentes. (C'est toujours intéressant, cela permet de progresser.)

Citation Envoyé par meziantou Voir le message
J'ai quelques remarques:
- Pourquoi passer les arguments en ref :
Code : Sélectionner tout
protected static T GetSingleDTO<T>(ref SqlCommand command) where T : DTOBase
Ici, c'est pour ne pas faire une copie locale de command dans la méthode GetSingleDTO, ce qui me semble relativement courant.

Citation Envoyé par meziantou Voir le message
- Pourquoi utiliser SqlConnection, SqlDataReader, etc. au lieu de IDbConnection, IDataReader, etc.
Je ne saurai te répondre directement.
Personnellement, j'ai créé une bibliothéque DAL générique qui me sert pour mes nouvelles appli. J'y ai intégré les DTO, aspect qui m'intéressait. J'ai transformé la méthode GetSingleDTO pour justement ne plus avoir de paramètre de type command.
Donc une approche avec IDbConnection, IDataReader... est, me semble-t-il, parfaitement envisageable.
J'avoue que je ne sais pas si l'utilisation d'interface peut avoir un impact sur les performances. (Le but étant d'être performant. Donc à voir sur des lectures nombreuses avec GetDTOList)
En tout état de cause pour avoir une réponse précise par rapport aux choix effectués par l'auteur tu peux poser la question sur son blog concernant l'article 2 : « High Performance Data Access Layer Architecture Part 2 ».

Citation Envoyé par meziantou Voir le message
Code : Sélectionner tout
if (reader.HasRows) { reader.Read();
Pourquoi ne pas directement faire
Code : Sélectionner tout
if(reader.Read())
Pour la méthode GetSingleDTO effectivement (peut être pour une meilleure lisibilité du code, l'impact d'un test étant négligeable). Par contre dans GetDTOList c'est utile puisque l'on effectue des traitements préparant la lecture.

Espérant avoir pu répondre en partie à tes remarques.
1  0 
Avatar de rv26t
Modérateur https://www.developpez.com
Le 20/11/2013 à 1:59
Citation Envoyé par meziantou Voir le message

Citation Envoyé par Kropernic Voir le message
Comme l'a souligné Hervé, il n'a fait qu'une traduction. Il n'est donc pas à blâmer (personne ne l'est en fait je crois).
Je ne le blâme pas, j'essaye juste de comprendre les choix techniques.
De plus il n'est pas interdit d'annoter la traduction, ce qui a mon sens ajouterais une vraie plus-value, surtout que le traducteur a fait des modifications pour créer sa DAL. Rien ne l'empêche d'expliquer ces modifications et pourquoi il les a faites.
Ne vous faites pas de soucis, je ne me sens pas blâmé. (comme je l'ai indiqué au début, je ne connais pas forcément la raison de certains choix de l'auteur)

J'ai simplement fait une traduction le plus fidèle possible des articles.

Je préférais laisser les lecteurs se faire leurs propres idées, et ne pas orienter leurs avis par des choix personnels. (Après la discussion est là pour échanger)
_______________________

Je travaille sur de petits projets. (Donc j'essaye d'anticiper le fait qu'un jour ma DAL pourra utiliser d'autres BDD.) Précision je travaille avec VB.

J'avais déjà créé ma DAL qui pouvait me renvoyer des DataSet, des DataTable, ou des info plus ciblées (une info avec ExecuteScalar, un dictionnaire, ...). Mais pour un ensemble de données typées, je retournais un jeu de données de type DbDataReader avec lequel je travaillais hors de ma DAL générique — une partie DAL appli — donc réécriture de code sensiblement le même sur le principe (il existe d'ailleur un article abordant le sujet de ce problème de réécriture fastidieux).

L'article m'a permit de trouver ce que je cherchais, les DTO.

J'ai intégré dans ma DAL que les méthodes GetSingleDTO et GetListDTO (pas le reste) en les transformant (pour utiliser les requêtes paramètrées). En créant une bibliothèque DTO de référence (DTOBase, DTOParser, et en transformant DTOParserFactory en une fabrique générique — sinon les méthodes GetSingleDTO et GetListDTO étaient inutilisables)

Il s'agit donc dans mon cas d'une utilisation personnelle très ciblée. Et je me suis servi de ces articles pour compléter l’existant. (voilà pour les explications)
_______________________

Ayant traduit les articles pour mes travaux personnels, je me suis dit que cela pouvais être utile à d'autres. Echanger des idées pour apprendre...

[Edit] Vu qu'il n'y a pas eu d'autres messages, je complète ma réponse.

Citation Envoyé par meziantou Voir le message
SqlCommand est une classe et non une structure...
Exact, j'ai été imprécis.
Il s'agit donc de ne pas faire une copie locale de la référence à command.
Je n'ai pas d'idée sur les performances. (Je vais regarder tes liens)
Vu l'utilisation de command qui est faite dans la méthode je ne vois pas ce qui pourrai orienter le choix du passage de paramètre par valeur ou par référence. (perso, je ne passe plus un paramètre de type command à mes méthodes GetSingleDTO et GetListDTO mais une requête, j'ai profondément modifié leurs utilisations pour les rendre très générique.)

Pour ceux qui souhaite une petite explication sur le passage de paramètre voir ce tuto : Passage de paramètres en C#
1  0 
Avatar de tomlev
Rédacteur/Modérateur https://www.developpez.com
Le 22/11/2013 à 20:04
Citation Envoyé par meziantou Voir le message
- Pourquoi passer les arguments en ref :
Code : Sélectionner tout
protected static T GetSingleDTO<T>(ref SqlCommand command) where T : DTOBase
Ça, c'est typique d'un développeur qui n'a pas bien compris les notions de type valeur/type référence/passage par valeur/passage par référence... Un peu surprenant dans ce cas, vu que l'auteur n'a rien d'un débutant a priori.

Ça ne sert effectivement strictement à rien de passer un type référence par référence, sauf bien sûr si la méthode affecte un autre objet au paramètre (ce qui n'est pas le cas ici).

Citation Envoyé par meziantou Voir le message
- Pourquoi utiliser SqlConnection, SqlDataReader, etc. au lieu de IDbConnection, IDataReader, etc.
Citation Envoyé par rv26t Voir le message
J'avoue que je ne sais pas si l'utilisation d'interface peut avoir un impact sur les performances
A priori ça ne change rien (ou pas grand chose) aux performances, mais ça permet surtout de faire abstraction du SGBD réel, et donc de pouvoir changer de provider facilement.
Après, cette abstraction est très bien dans le principe, mais malheureusement dans la pratique les SGBD ne sont pas tous équivalents, donc on se retrouve finalement toujours à devoir faire du code spécifique car les dialectes SQL sont un peu différents d'un SGBD à l'autre...
1  0 
Avatar de Kropernic
Expert confirmé https://www.developpez.com
Le 19/11/2013 à 8:56
Hello,

C'est vrai que quand j'ai découvert ces articles, je ne m'étais pas trop posé de question à l'époque (étant un noob en POO, ça fonctionnait bien alors j'ai dit amen ).

Maintenant, à la vue des questions soulevées par meziantou, je m'interroge également.

Concernant le passage par référence :
Hervé donné une piste. Maintenant, à moins d'avoir une boucle qui, pour une raison x ou y, irait exécutait un certain nombre de fois la méthode, je ne vois pas trop le problème de faire un passage par valeur.
Je remarque aussi que dans la méthode GetSingleDTO (idem pour GetDTOList), on ferme et libère la connexion. Alors je ne sais pas vous mais perso dans mes classes DAL, ça donne quelque chose du genre :
Code : Sélectionner tout
1
2
3
4
5
6
7
8
 
Dim obj as DTO.MonObjet 
Using cmd as SqlCommand = CreateSpCommand("STORED_PROCEDURE_NAME") 
    cmd.Parameters.Add(CreateParameter("@PRM_NAME", prm_value)) 
 
    obj = GetSingleDTO(Of DTO.MonObjet)(cmd) 
End Using 
Return obj
A la destruction de l'objet command par le End Using, la connection est automatiquement fermée et libérée non ?

Concernant les types pour les objets DB (SqlConnection, SqlCommand, etc.) :
Pour avoir lu d'autre de ces articles, je sais que l'auteur travaille avec SQL SERVER. Je pense qu'il ne faut pas chercher plus loin. Mais voir une implémentation plus générique m'intéresserait. Je suis d'ailleurs en total désaccord avec lui quand, dans un autre article, il explique qu'il ne perd plus son temps à faire une couche DAL séparée et qu'il mixe tout dans la BLL car, après tout, changer de DB n'arrive que très rarement dans la vie d'un développeur.

Concernant If reader.HasRows() :
Personnellement, je préfère avoir ce test en plus. Je trouve cela plus lisible. Il ne faut pas perdre de vue que, lorsque nous programmons, ce ne sera pas forcément nous qui devront maintenir le code. Et puis ça me rappelle mes cours de méthodologie où mon prof répétait sans cesse : "Je prépare, j'utilise". Donc ici, on prépare le reader (on vérifie s'il contient des données) et on l'utilise ensuite (on lit les données qu'il contient).

Comme l'a souligné Hervé, il n'a fait qu'une traduction. Il n'est donc pas à blâmer (personne ne l'est en fait je crois). Mais ne perdons pas de vue que l'auteur n'est pas un dieu non plus. Il est donc fort possible que ce qu'il a partagé avec nous sur son blog puisse être sujet à amélioration. Je vois plus ces articles comme une bonne base de travail sur laquelle s'appuyer pour progresser.
0  0 
Avatar de meziantou
Membre émérite https://www.developpez.com
Le 19/11/2013 à 11:33
SqlCommand est une classe et non une structure, je ne comprend donc pas le gain de performance en utilisant ref.
http://stackoverflow.com/questions/8110981/c-sharp-the-performance-cost-to-using-ref-instead-of-returning-same-types
http://stackoverflow.com/questions/3395873/pass-by-value-vs-pass-by-reference-performance-c-net

Pour l'utilisation de Using(...) ça ne change pas grand chose: le using va être transformé en
Code : Sélectionner tout
try {} finally {obj.Dispose();}
Le dispose ferme la connexion. Le résultat est le même (même si moi aussi je préfère utiliser using)

Je suis d'ailleurs en total désaccord avec lui quand, dans un autre article, il explique qu'il ne perd plus son temps à faire une couche DAL séparée et qu'il mixe tout dans la BLL car, après tout, changer de DB n'arrive que très rarement dans la vie d'un développeur.
Je suis plutôt d'accord avec sa façon de faire, à condition de n'utiliser que des procédures stockées (D'ailleur j'avais déjà défendu cette vision dans un autre post qui parlait DAL, DTO, POCO, et je ne sais plus quels acronymes). En effet même si tu changes de base de données, la couche d'accès reste la même à 1 ou 2 détails près (par exemple certains SGBD ne supporte pas les noms de plus de 30 caractères)

Comme l'a souligné Hervé, il n'a fait qu'une traduction. Il n'est donc pas à blâmer (personne ne l'est en fait je crois)
Je ne le blâme pas, j'essaye juste de comprendre les choix techniques.
De plus il n'est pas interdit d'annoter la traduction, ce qui a mon sens ajouterais une vraie plus-value, surtout que le traducteur a fait des modifications pour créer sa DAL. Rien ne l'empêche d'expliquer ces modifications et pourquoi il les a faites.
0  0