none
Multi-couche : un os

    Question

  • Bonjour tout le monde,

    Je me mets au développement multi-couche, DAL/BAL ...

    Le principe qui m'a été présenté s'appuie sur une procédure stockée pour retourner les objets qu'on veut interroger.

    Ah ben oui, mais ... Si je ne m'abuse, une procédure stockée, on peut lui faire retourner un entier par un paramètre out, mais ... un objet c'est plus compliqué.

    Il y a les fonctions utilisateur de type table, qui sont bien adaptées pour faire ça, mais c'est plus délicat à appeler depuis C#.

    C'est tellement gros que je me suis dit, le gars a prévu une procédure stockée, ça doit marcher. Mais effectivement, une fois que j'ai bien jointoyé les classes qui s'appellent, en les mettant dans les bons espaces de noms, il m'est bien objecté que l'objet n'est pas implémenté, sur ce code :

                SqlCommand command = new SqlCommand(sprocName);
                command.Connection = SharedConnection;
                command.CommandType = CommandType.StoredProcedure;
                return command;

    et effectivement, je n'ai pas de procédure stockée qui porte le nom que j'ai indiqué, puisque c'est celui d'une fonction de type table.

    CommandType peut indiquer qu'on a affaire à une table, mais je ne m'attends pas spécialement à ce que ça appelle une fonction définie par l'utilisateur.

    Suis-je en train de me noyer dans une goutte d'eau ?

    Pour être franc je n'ai pas essayé de faire retourner une table par une procédure stockée, car j'ai fait confiance à la doc. Ai-je eu tort ?


    • Modifié Gloops lundi 25 avril 2016 07:53
    lundi 25 avril 2016 07:51

Réponses

  • Bonjour,

    J'ai donc téléchargé la solution de là

    https://www.simple-talk.com/dotnet/.net-framework/.net-application-architecture-the-data-access-layer/

    et c'était beaucoup plus simple puisque je n'avais pas à me demander ce qui se met où.

    En revanche, il va falloir que je lui fasse signe pour une erreur, puisqu'il remplace toutes les valeurs nulles par la valeur minimale du type correspondant, donc autant dire que pour les produits de la base AdventureWorks ça ne le fait pas, puisque concernant la période de vente, la date de fin doit être supérieure à la date de début ou nulle. Donc si on remplace les valeurs nulles par les valeurs minimales ...

    A part ceci ce projet permet facilement d'afficher/modifier les données dans des textboxes.

    • Marqué comme réponse Gloops jeudi 28 avril 2016 09:15
    mercredi 27 avril 2016 11:39

Toutes les réponses

  • Bonjour,

    Je ne sais pas quel principe vous a été présenté, mais je pense que vous faîtes une confusion entre ce que doit faire votre base et ce que doit faire votre couche "DAL".

    Dans le développement multi-couche chaque couche est "indépendante" et fait le lien entre sa couche supérieure et inférieure.

    La DAL va faire le lien entre vos source de données et votre application (plus exactement la BLL dans votre cas qui sera appelée par votre application) en gérant la transformations des données venant de votre base (ou fichier externe, ou API, etc.) en objet de votre application et réciproquement. Typiquement un ORM (Entity Framework, NHibernate,...) à cette fonction. Cela permet de faire une abstraction de votre source de données, vous posez une question à votre DAL (liste des clients actifs) et en retour elle se débrouille pour vous retournez une liste d'objet 'Client'. Comment elle l'a obtenu ? C'est son problème.

    Pour mettre en œuvre une DAL quand on ne passe pas par un ORM, c'est de faire des requêtes SQL, et de transformer les données reçues en objet. Maintenant que vous passez par une requête, une vue ou une procédure stockée peut importe du moment que votre DAL récupère les données.  Ensuite à vous de créer les méthodes qui vont convertir les données reçues en objet.

    Tout comme l'inverse, vous demandez d'enregistrer les modifications d'un Client, à vous de transformer les valeurs de l'objet en requête SQL qui va mettre à jour les données. D'où l'intérêt des ORM qui se chargent de tout cela à notre place.

    Dans le cas qui vous préoccupe, je suppose que votre procédure stockée est sensée renvoyer un résultat de données, c'est la DAL qui doit transformer ce résultat en objet. Ce n'est pas à votre base de faire ce travail (ce n'est pas sa fonction). Bin entendu votre DAL n'est pas obligé de renvoyer un objet, elle peut appeler une procédure stockée qui renvoi un résultat scalaire (valeur seule, au lieu d'un enregistrement) et va renvoyer cette valeur ou l'interpréter en fonction des besoins.

    Cordialement,


    Yan Grenier

    Merci de bien vouloir "Marquer comme réponse", les réponses qui ont répondues à votre question, et de noter les réponses que vous avez trouvé utiles.

    lundi 25 avril 2016 09:01
  • Je me suis basé là-dessus :

    http://rlacovara.blogspot.fr/2009/03/high-performance-data-access-layer-part.html

    J'ai mis la page 3, c'est la seule qui a les liens vers les deux autres. Une page est traduite en Français sur developpez.net

    Mettre une requête enregistrée dans la base ne me paraissait pas trop faux, au moins on est sûr d'avoir une syntaxe adaptée à la base, et d'avoir vu ce qu'elle sort. A priori si le gars s'est donné le mal de développer le fonctionnement avec une procédure stockée je suppose que ça devrait marcher ? Surtout qu'apparemment ils sont un paquet à être rodés à la question et ils ont l'air enthousiastes comme tout, ça paraît difficile à croire qu'il y ait une lacune si bloquante.

    J'ai bien compris le principe des différentes couches, pour le moment je n'ai développé que les deux couches du bas pour voir ce qui remonte, après quand il n'y a plus que l'interface utilisateur à développer c'est plus de la routine.

    Quand j'ai vu objet non instancié je me suis demandé de quoi il retournait, et après quand j'ai vu sur quoi ça portait je n'étais pas étonné.

    Il doit y avoir moyen de fournir le code de la requête depuis l'application, effectivement. C'est quand même dommage, il faut bien avouer.


    • Modifié Gloops lundi 25 avril 2016 09:30
    lundi 25 avril 2016 09:29
  • Haa je comprend mieux :)

    La solution que propose Rudy est totalement dans ce que j'ai énoncé, il a créé un ORM qui est capable de convertir le résultat d'une requête en objet (type DTOBase dans son cas).

    En revanche il est vrai que son exemple il utilise uniquement des procédures stockées (ce qui est une bonne pratique SQL Server), et malheureusement il ne montre pas d'exemple de procédure stockées (en tout cas je n'en ai pas vu). Ce type de procédures stockées font simplement un "SELECT" qui retourne un résultat de données.

    De toute manière normalement son code fonctionne également avec un SqlCommand qui est une requête SQL. Dans les deux cas un résultat de données est retourné.

    Cordialement,


    Yan Grenier

    Merci de bien vouloir "Marquer comme réponse", les réponses qui ont répondues à votre question, et de noter les réponses que vous avez trouvé utiles.

    lundi 25 avril 2016 09:47
  • Effectivement il semble que j'aie fait trop confiance à la doc SQL Server. En créant une procédure stockée avec le même nom et le même code que la fonction table, j'obtiens aussi les enregistrements en retour, comme je ne m'y attendais pas du tout.

    Du coup l'erreur s'interprète différemment : beaucoup d'objets et méthodes dans l'application ne sont pas instanciés, mais ils ne sont pas non plus marqués static. Alors, il arrive un moment où il faut choisir.

    lundi 25 avril 2016 12:33
  • Une procédure stockée est un bloc de code qui effectue des traitements sur le serveur SQL. Elle peut renvoyer des ensembles de données qui peuvent être utilisée par l'application appelante.

    Une fonction table "génère" un ensemble de donnée, mais à la différence d'une  procédure stockée, elle est utilisée comme source dans une clause FROM SQL, d'où son nom fonction "Table". Si vous voulez utiliser le résultat de cette fonction vous devez faire un SELECT sur cette fonction pour obtenir les données.

    Ce n'est pas un problème de confiance auprès de la doc, c'est plus un problème d'utiliser le bon outil pour le bon besoin :)

    Pour la seconde partie, je ne comprends pas ce que vous voulez-dire? Quel est le rapport entre votre erreur et des objets non instanciés ?

    Cordialement,


    Yan Grenier

    Merci de bien vouloir "Marquer comme réponse", les réponses qui ont répondues à votre question, et de noter les réponses que vous avez trouvé utiles.


    lundi 25 avril 2016 12:49
  • Une procédure stockée est un bloc de code qui effectue des traitements sur le serveur SQL. Elle peut renvoyer des ensembles de données qui peuvent être utilisée par l'application appelante.

    Une fonction table "génère" un ensemble de donnée, mais à la différence d'une  procédure stockée, elle est utilisée comme source dans une clause FROM SQL, d'où son nom fonction "Table". Si vous voulez utiliser le résultat de cette fonction vous devez faire un SELECT sur cette fonction pour obtenir les données.

    Ce n'est pas un problème de confiance auprès de la doc, c'est plus un problème d'utiliser le bon outil pour le bon besoin :)

    Dire qu'il aura fallu que je m'attaque au traitement multi-couche pour prendre conscience de ça ...

    Pour la seconde partie, je ne comprends pas ce que vous voulez-dire? Quel est le rapport entre votre erreur et des objets non instanciés ?


    Pour parler franchement j'ai donné du static à droite et à gauche un peu au petit bonheur la chance (et beaucoup à mauvais escient je crois), donc je vais devoir citer de mémoire.

    Le message était quelque chose comme "cet objet n'est pas initialisé à une instance d'un objet". C'est pour ça qu'au départ j'avais compris que c'était la procédure stockée qu'on ne trouvait pas, et ... ça n'avait rien à voir, si on appelle une propriété d'instance sur la classe d'un objet on a ça comme erreur. Et donc dans ce cas-là il y a deux cas de figure, ou on a oublié d'instancier, ou on a oublié de déclarer static la propriété (ou la méthode). Cela étant j'ai déjà interprété de travers le message, il n'est pas exclu que je sois parti sur une deuxième voie de traverse ...

    Quelqu'un d'autre a proposé une solution toute prête et opérationnelle, avec les éléments proprement séparés en projets, ça va me permettre de regarder comment c'est fait sans avoir à me demander en lisant une instruction dans quel fichier je dois la mettre. Je crois bien que j'avais fini, d'ailleurs, pour le plus gros, à mettre ce qu'il fallait où il fallait, mais manifestement il reste encore quelques erreurs, et ça promet de ne pas être simple de me dire quoi sans savoir au juste ce que j'ai fait.

    Quand c'était des objets qui n'étaient pas dans l'espace de noms où on les attendait c'était facile à corriger, après il y a d'autres messages qui peuvent dérouter plus, et je n'en vois qu'un à la fois, donc j'ignore totalement combien d'autres messages d'erreur m'attendent successivement.

    Sans compter qu'il y a un peu de vrac dans les références du projet aussi, et du coup une bonne partie de l'IntelliSense est inactive. Pour faire une démo avec ça, je ne me sens pas très fier ...

    lundi 25 avril 2016 13:34
  • Mieux vaut tard que jamais :)

    Je comprends mieux votre message précédent. En effet que vous avez du vous "mélanger les crayons" entre des membres statiques et des membres d'instances. De manière générale pour résoudre de type de problème, il est préférable d'essayer de comprendre le but du code, sinon vous allez passer votre temps a ajouter/retirer du static un peu partout (mais ca peut marcher sur un coup de chance :)).

    Les mises en œuvre de type "Muti-Couche" simplifie la maintenance d'une application, mais peuvent complexifier le "flux" de traitement. Et on peut s'y perdre parfois. C'est pour cela qu'il important de bien saisir le rôle de chaque élément logique (chaque couche) et physique (chaque objet) pour être capable de déterminer qui fait quoi lors du développement. Donc il ne faut pas hésiter à tester le code de demo (au pas a pas si nécessaire) pour comprendre ce chacun fait. Ce qui eut également aider sur les choix d'implémentation (pourquoi on utilise du static à certains endroits, pourquoi on utilise des interfaces, etc...).

    Cordialement,


    Yan Grenier

    Merci de bien vouloir "Marquer comme réponse", les réponses qui ont répondues à votre question, et de noter les réponses que vous avez trouvé utiles.

    lundi 25 avril 2016 13:51
  • En effet. Les explications sont rédigées de façon claire, mais il me fallait d'abord les mémoriser avant d'être capable de situer où se trouve le code et dans quel fichier le mettre. Et du coup j'ai tout mis dans App_Code, on fait mieux dans ce domaine.

    Partir de quelque chose qui fonctionne et regarder comment c'est fait, j'ai l'impression que ça devrait mieux me réussir. Il y a beaucoup moins d'explications avec, mais comme les explications je les ai déjà lues ...

    Je suis à la bourre, mais au pire ça sera pour une autre fois.


    • Modifié Gloops jeudi 28 avril 2016 09:22
    lundi 25 avril 2016 14:02
  • Bonjour,

    Petite remarque sur le code présenté : j'ai un gros doute (mais un vrai gros doute ;-)) sur le try catch.

    1/ Il fait un catch pour tout type d'exception. Si il y a un pb dans la base de données, si elle est non disponible par exemple (timeout), le code retourné sera un "Error populating data" ! Ce qui dans cet exemple ne sera pas vrai. Ok dans le InnerException on aura la vrai info, mais je vous pari $10 que le code client ne l'utilisera jamais.

    De plus, pourquoi y aurait il une exception ? Soit le développeur n'a pas testé son code et il peut effectivement y avoir un pb de cast quelque part dans le populating. Mais ca, c'est un bogue donc il faut le corriger avant de la mettre en prod.

    Donc si il y a pb, c'est que c'est grave (encore une fois, bdd non dispo). Et dans ce cas, il est préférable que la couche data "plante" quitte à la catcher dans l'interface (si l'on sait quoi faire en cas de plantage).

    2/ Le SqlCommand est passé en paramètre ce qui est une très mauvaise idée. La classe PersonDb instancie le SqlCommand, le passe a la méthode de la classe de base qui tranquillement la détruit. Pas cool ;-)

    Bref, je trouve pas cet exemple très pertinent.

    My2cts


    Richard Clark
    Consultant - Formateur .NET
    http://www.c2i.fr
    Depuis 1996: le 1er site .NET francophone

    mardi 26 avril 2016 07:47
  • Bonjour,

    Petite remarque sur le code présenté : j'ai un gros doute (mais un vrai gros doute ;-)) sur le try catch.

    1/ Il fait un catch pour tout type d'exception. Si il y a un pb dans la base de données, si elle est non disponible par exemple (timeout), le code retourné sera un "Error populating data" ! Ce qui dans cet exemple ne sera pas vrai. Ok dans le InnerException on aura la vrai info, mais je vous pari $10 que le code client ne l'utilisera jamais.

    De plus, pourquoi y aurait il une exception ? Soit le développeur n'a pas testé son code et il peut effectivement y avoir un pb de cast quelque part dans le populating. Mais ca, c'est un bogue donc il faut le corriger avant de la mettre en prod.

    Donc si il y a pb, c'est que c'est grave (encore une fois, bdd non dispo). Et dans ce cas, il est préférable que la couche data "plante" quitte à la catcher dans l'interface (si l'on sait quoi faire en cas de plantage).

    Je suis parti sur un autre exemple, avec une solution fournie déjà fonctionnelle, où le fournisseur est aussi allé un peu vite sur deux points, effectivement la gestion d'exceptions d'une part, d'autre part sur l'interface utilisateur la possibilité de revenir à l'écran de départ.

    Comme il s'adresse à des développeurs qui savent développer ces points, on va dire que ça ne nuit pas nécessairement à la compréhension du sujet.

    Ce qui m'a encouragé à chercher un autre exemple, est que le style qui fait des aller-retour entre les différentes couches, nécessite une capacité de concentration sans faille sur tout le temps de la lecture pour mémoriser les différents endroits pointés dans le document, ce qui s'est trouvé me faire défaut à un moment.

    Quand j'aurai fini, je tâcherai de trouver le temps d'exploiter à nouveau ce premier projet, pour intégrer dans ce que j'aurai fait la gestion des ordinaux (peut-être y a-t-il une meilleure traduction ?) pour éviter au pilote de chercher d'après les noms de champs. Rudy Lacovara dit que ça lui prend plus de temps, ça me paraît plausible. Pour en avoir le cœur net il faudrait écrire une application de test, par exemple.

    2/ Le SqlCommand est passé en paramètre ce qui est une très mauvaise idée. La classe PersonDb instancie le SqlCommand, le passe a la méthode de la classe de base qui tranquillement la détruit. Pas cool ;-)

    Ah, j'avoue que ce point n'avait pas éveillé mon attention. Mais j'en étais encore à chercher le fil d'ensemble ...


    • Modifié Gloops mardi 26 avril 2016 08:22
    mardi 26 avril 2016 08:09
  • Un conseil : ne tombez pas dans le piège de construire un "Framework" propriétaire.

    Généralement (dans 100% des cas que j'ai étudié) on passe plus de temps sur le Framework que sur le vrai code de l'appli.


    Richard Clark
    Consultant - Formateur .NET
    http://www.c2i.fr
    Depuis 1996: le 1er site .NET francophone

    mardi 26 avril 2016 08:12
  • Je commence par la solution de facilité : j'adapte la solution téléchargée à une autre base de données, une autre table. ça m'assurera au moins d'avoir bien assimilé les deux couches du bas.

    La solution est divisée en quatre projets, qui s'appellent Demo.DataAccessLayer.SQL, Demo.Common, Demo.Business, Website. Quand on lit un code on visualise très bien dans quelle couche on est. C'est reposant.


    • Modifié Gloops mardi 26 avril 2016 08:55
    mardi 26 avril 2016 08:41
  • Bonjour,

    J'ai donc téléchargé la solution de là

    https://www.simple-talk.com/dotnet/.net-framework/.net-application-architecture-the-data-access-layer/

    et c'était beaucoup plus simple puisque je n'avais pas à me demander ce qui se met où.

    En revanche, il va falloir que je lui fasse signe pour une erreur, puisqu'il remplace toutes les valeurs nulles par la valeur minimale du type correspondant, donc autant dire que pour les produits de la base AdventureWorks ça ne le fait pas, puisque concernant la période de vente, la date de fin doit être supérieure à la date de début ou nulle. Donc si on remplace les valeurs nulles par les valeurs minimales ...

    A part ceci ce projet permet facilement d'afficher/modifier les données dans des textboxes.

    • Marqué comme réponse Gloops jeudi 28 avril 2016 09:15
    mercredi 27 avril 2016 11:39
  • Après, il reste un sujet entier, qui vient aussi se greffer sur la gestion multi-couches.

    Pour une base avec plus de champs, j'ai trouvé ça pratique de l'afficher dans  un GridView.

    Afficher, ça va tout seul. Mais c'est quand j'essaie de modifier ...

    J'ai longtemps buté sur le fait que je n'avais pas les OldValues et NewValues pendant le RowEditing, et j'ai redouté que ça coince durablement, puisque j'ai trouvé à profusion des fils de forums ouverts là-dessus en 2008, où les gens n'ont semble-t-il toujours pas trouvé la solution.

    Finalement, j'ai compris que passer le nom d'une requête enregistrée pour la sélection ça ne le fait pas vraiment, pusque ASP.Net cherche les noms des champs dans la requête pour "peupler" les OldValues et NewValues.

    Pour bien faire il faudrait passer la requête de sélection non au GridVeiw mais à l'ObjectDataSource, qu'on passe à son tour à la GridView. Mais, comme ça je n'ai pas pu faire d'affichage, alors qu'en passant directement le retour de la requête object ça affiche très bien, c'est la modif qu ne passe pas. C'est un peu gonflé comme terme la requête objet, je parle des résultats obtenus par l'intermédiaire d'une approche multi-couche.

    Est-ce qu'on continue ici, ou j'ouvre un autre fil ?


    • Modifié Gloops mercredi 27 avril 2016 12:22
    mercredi 27 avril 2016 11:44
  • Autre fil

    (et n'oubliez pas de marquer les réponses qui vous semblent pertinentes)


    Richard Clark
    Consultant - Formateur .NET
    http://www.c2i.fr
    Depuis 1996: le 1er site .NET francophone

    mercredi 27 avril 2016 12:08