Date de publication : 06/02/08 Dernire mise jour : 06/02/08 Cet article a deux objectifs : 1 Prsenter quelques manires d'crire un code gnrique pour les accs aux donnes (.NET 1.1 et 2.0) 2 Rappeler et expliquer les avantages des paramtres SQL dans le cadre des requtes SQL (et des procdures stockes). Bonnes pratiques pour les accs aux donnes par Johann Blais - 2 - Ce document est issu de http://www.developpez.com et reste la proprit exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise l'obtention pralable de l'autorisation de l'auteur. http://johannblais.developpez.com/tutoriel/dotnet/bonnes-pratiques-acces-donnees/ I - Introduction II - Abstraction des classes en .NET 1.1 III - Abstraction des classes en .NET 2.0 IV - Utilisation de requtes paramtres IV-A - Le problme IV-B - La solution V - Conclusion VI - Remerciements Bonnes pratiques pour les accs aux donnes par Johann Blais - 3 - Ce document est issu de http://www.developpez.com et reste la proprit exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise l'obtention pralable de l'autorisation de l'auteur. http://johannblais.developpez.com/tutoriel/dotnet/bonnes-pratiques-acces-donnees/ I - Introduction Les bases de donnes sont aujourd'hui monnaie courante, et ce depuis un certain temps d'ailleurs. Avec chaque nouveau langage, arrive son lot de nouvelles fonctions, de nouvelles manires de travailler avec des bases. Nous (dveloppeurs, analystes, etc) avons en nous ce qui mon avis est notre plus grande qualit et notre plus grand dfaut, nous sommes paresseux. Avant de me jeter des cailloux, laissez moi dvelopper un peu sur ce point. Vous vous tes tous dit un jour ou l'autre, "j'aimerais bien faire a, mais bon il y a srement dj quelqu'un qui a eu la mme ide, et qui, dans sa grande bont, a dcid d'en faire profiter la communaut"; on peut d'ailleurs y voir l'origine du "Google is your friend". Disons les choses comme elles sont, dans ce cas, tre fainant c'est bien, c'est mme trs bien. Mais, car il y a toujours un mais, cette qualit se retourne souvent contre nous. J'en prends pour exemple ce collgue que nous avons tous, celui qui fait partie des dinosaures, a connu l'informatique ses dbuts, pluchait les listings en LISP et en ADA des magazines de l'poque; je suis sr que vous voyez de qui je parle. Et bien, ce collgue dont vous riez en cachette (ou ouvertement pour les plus tmraires), est un feignant dans le mauvais sens, il a gard ses principes de dveloppement ant-diluviens. Le problme est que l'on a tendance prendre nos petites habitudes, assez vite les bonnes et malheureusement trs vite les mauvaises. J'cris cet article avec pour objectif de vous donner quelques trucs, quelques voies emprunter pour vous faciliter la vie ou plutt le dbogage. Car avoir les bonnes mthodes ds le dbut vous permet de gagner en rapidit, et surtout en efficacit. Je commencerai par vous parler d'abstraction des mthodes d'accs, et je terminerai par les bonnes mthodes pour excuter une requte en fonction de la situation. Bonnes pratiques pour les accs aux donnes par Johann Blais - 4 - Ce document est issu de http://www.developpez.com et reste la proprit exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise l'obtention pralable de l'autorisation de l'auteur. http://johannblais.developpez.com/tutoriel/dotnet/bonnes-pratiques-acces-donnees/ II - Abstraction des classes en .NET 1.1 Nombre d'entre vous ont dj dvelopp des projets impliquant plusieurs sources de donnes, comme par exemple une base Oracle d'un ct, une base SqlServer de l'autre, et souvent plusieurs bases de chaque. Vous savez donc combien il peut tre fastidieux de toujours avoir ouvrir une connexion, crer une requte, l'excuter, parcourir les donnes et... recommencer avec la base suivante. Car la plupart du temps, vous devez rcuprer de nombreuses donnes en mme temps et plusieurs endroits. Rsultat, vous avez cinquante lignes de code qui servent rcuprer les donnes, et vous n'avez mme pas encore effleur l'aspect mtier de la mthode. Cette approche peut fonctionner dans le cadre d'un projet "Papa/Maman", mais dans la vraie vie, vous en conviendrez, ce n'est pas ce qu'on peut qualifier de "professionnel". Pour ceux d'entre vous qui s'impatientent, j'en viens ce qui vous intresse : le CODE. Je ne vais cependant pas vous submerger avec des lignes et des lignes n'en plus finir. Mais intressons nous certaines structures disponibles dans le framework. Les framework .NET (toutes les versions) possdent dans le namespace System.Data, toute une collection d'interfaces destines nous faciliter la vie. Voici la liste des plus utiles : a IDbConnection b IDbCommand c IDbParameter d IDbTransaction e IDbDataAdapter... La plupart d'entre vous auront dj reconnu les types d'objet qu'ils manipulent quotidiennement mais dans leur forme concrte (SqlConnection, OracleDataReader, etc). Quel est alors l'intrt pratique d'utiliser les interfaces plutt que les implmentations. Prcisons d'abord que mme en utilisant les interfaces, il faut bien un moment ou un autre leur associer une implmentation (SqlServer, Oracle, autre), mais nous reviendrons sur ce point plus tard. L'utilisation des interfaces permet d'crire un code qui ne dpend pas de la base de donnes sur laquelle il sera excut. Je suis sr que certain(e)s d'entre vous ont dj eu changer de base de donnes en cours de projet. Si on n'est pas bien prpar, a peut vite tourner la dpression nerveuse. Passons de suite un exemple concret. Vous tes un dveloppeur prvoyant, vous avez dj cr une classe qui vous renvoie une commande initialise partir d'une chane de caractres contenant le code SQL, et comme vous tes encore plus prvoyant, vous passez aussi votre mthode une enum qui permet mme de slectionner la source de donnes de destination. Vous avez donc une mthode qui a cette tte l : public SqlCommand CreateCommand(string commandText, connectionType dataSource); L j'ai envie de vous dire "c'est pas mal", c'est dj mieux que ce qu'crivent 90% des dveloppeurs. Vous utilisez donc joyeusement cette mthode partout dans votre code, et vous avez raison. Le problme c'est que comme tout le monde, vous avez un chef qui a tendance courir en agitant les bras dans tous les sens au moindre problme. Et l justement le problme c'est que SqlServer, on en a un peu assez, les clients veulent du Oracle. Qu' cela ne tienne, vous migrez votre base, et vous attaquez maintenant modifier votre code. Vous transformez videmment votre prcieuse mthode en : public OracleCommand CreateCommand(string commandText, connectionType dataSource); Bonnes pratiques pour les accs aux donnes par Johann Blais - 5 - Ce document est issu de http://www.developpez.com et reste la proprit exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise l'obtention pralable de l'autorisation de l'auteur. http://johannblais.developpez.com/tutoriel/dotnet/bonnes-pratiques-acces-donnees/ Et l, c'est le drame, il faut modifier le code tous les endroits qui appellent cette mthode (heureusement il n'y en a que 1500), sans compter les SqlDataReader, SqlParameter, etc modifier par la mme occasion. a fait mal. Revenons au moment de la cration de la mthode Flashback. Vous savez que OracleCommand et SqlCommand implmentent l'interface IDbCommand. Au lieu d'utiliser un type spcifique, vous allez modifier votre PM (prcieuse mthode) pour qu'elle renvoie un IDbCommand la place. La signature de votre PM devient : public IDbCommand CreateCommand(string commandText, connectionType dataSource); Je prcise aussi que vous ne modifiez rien d'autre dans la PM, simplement le type de retour dans la signature. Vous utilisez votre PM aux mmes endroits et comme vous utilisez des IDbCommand dans la PM, vous continuez sur votre lance, et utilisez des IDbParameter et des IDataReader, partout o il faut. On revient au point critique, le changement de base de donnes. Au dbut mme chose, migration de la base de donnes, etc. Et vous en arrivez aussi au moment de modifier votre code. Vous commencez aussi par votre PM. Le type de retour ne change pas, OracleCommand implmente aussi IDbCommand. Il suffit de remplacer dans le corps de la PM, les objets Sql* par des objets Oracle*. Et l, miracle, c'est tout. Tout fonctionne exactement pareil. Pas de rechercher/remplacer sauvage, pas de prise de tte, changements rduits au minimum. Bref votre chef est content ! A ce moment, vous avez fait un constat encore plus accablant, le changement de base de donnes peut n'avoir pour consquence que le changement de l'objet connexion. En effet, on peut crer une commande en utilisant la mthode CreateCommand de l'interface IDbConnection. En admettant que vous soyez familier avec le design pattern "factory", vous avez compris que le changement de base de donnes peut n'avoir comme un impact que l'ajout d'un case dans la mthode qui cre la connexion en fonction de la source de donnes. Voici un exemple plus complet : public IDbConnection CreateConnection(connectionType type) { IDbConnection connection = null; switch(type) { case connectionType.MaBaseOracle : connection = new OracleConnection(maChaineDeConnexion); case connectionType.MaBaseSqlServer : connection = new SqlConnection(maChaineDeConnexion); } return connection; } Pour ceux qui ne veulent pas forcment grer plusieurs sources de donnes, il suffit de simplement de supprimer le switch et de retourner directement la SqlConnection (ou OracleConnection, OleDbConnection). Voici un exemple de ce que pourrait tre votre PM : public IDbCommand CreateCommand(connectionType type, string commandText) { // Ajouter la gestion des cas d'erreur return CreateConnection(type).CreateCommand(); } Bonnes pratiques pour les accs aux donnes par Johann Blais - 6 - Ce document est issu de http://www.developpez.com et reste la proprit exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise l'obtention pralable de l'autorisation de l'auteur. http://johannblais.developpez.com/tutoriel/dotnet/bonnes-pratiques-acces-donnees/ Je ne vais pas trop dtailler les diffrentes interfaces, vous avez saisi le principe, je vais simplement rappeler quelques mthodes utiles : // Cre un DataReader IDataReader reader = maCommande.ExecuteReader(); ... // Cre un IDataParameter pour une requte IDataParameter param = maCommande.CreateParameter(); Pour plus d'informations la documentation du framework contient tout ce dont vous avez besoin. Mme si cette question concerne principalement le framework 1.1, l'approche utilise est applicable pour les versions suprieures du framework. Cependant partir de la version 2.0, nous avons notre disposition des outils plus puissants. Nous allons tout de suite en parler. Bonnes pratiques pour les accs aux donnes par Johann Blais - 7 - Ce document est issu de http://www.developpez.com et reste la proprit exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise l'obtention pralable de l'autorisation de l'auteur. http://johannblais.developpez.com/tutoriel/dotnet/bonnes-pratiques-acces-donnees/ III - Abstraction des classes en .NET 2.0 Nous venons de voir de quelle manire crire du code gnrique en .NET 1.1 en utilisant les interfaces du namespace System.Data. A partir du framework 2.0, un namespace a t considrablement mis jour : System.Data.Common. En version 1.1, ce namespace ne contenait que quelques classes parmi lesquelles DataAdapter, pour ne citer que la plus utilise. Voyons sans plus attendre les nouveauts de System.Data.Common en .NET 2.0. En faisant un tour dans la MSDN, on peut avoir une vue d'ensemble des classes ajoutes. La majorit des classes est nomme Db[quelquechose], ceci donne dj une vague ide de leur utilit, en un mot, cela concerne les bases de donnes (DB), mais ne donne pas d'indication sur le SGBD en question ( l'inverse des OracleConnection ou SqlCommand auxquels nous sommes habitus). Toutes ces classes sont en effet des classes gnriques destines tendre l'utilisation des interfaces. La premire chose remarquer est que les classes gnriques implmentent ces mmes interfaces, ce qui explique que le code crit dans la section prcdente fonctionne en l'tat en .NET 2.0, ce qui est une bonne chose lors des migrations vers le framework 2.0. La principale nouveaut concerne la notion de "provider" (fournisseur). Cette notion est matrialise par les classes DbProviderFactories et DbProviderFactory. Pour la petite information, DbProviderFactories est une " usine abstraite " ou " Abstract Factory " (en anglais, a donne l'air de mieux s'y connatre). Il s'agit d'un design pattern, c'est en quelque sorte une super Factory. L'ide de l'abstract factory est d'encapsuler un groupe de factory ayant un thme commun. L'objectif dans le cas de DbProviderFactory est de pouvoir obtenir une factory dpendante de la source de donnes sans pour autant devoir connatre le type mme de cette factory. Bonnes pratiques pour les accs aux donnes par Johann Blais - 8 - Ce document est issu de http://www.developpez.com et reste la proprit exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise l'obtention pralable de l'autorisation de l'auteur. http://johannblais.developpez.com/tutoriel/dotnet/bonnes-pratiques-acces-donnees/ Illustration 1: Schma de l'abstract factory DbProviderFactory DbProviderFactories permet d'numrer les fournisseurs disponibles pour l'application (ex : SQL Server, Oracle, MySQL, SQL Server Compact Edition, etc). Elle est aussi responsable de la cration des gnrateurs lis une source de donnes spcifique. DbProviderFactory reprsente un gnrateur d'objets lis un fournisseur donn. La classe contient des mthodes pour crer des DbCommand, DbConnection, DbParameter. Ces objets sont dpendant du fournisseur, cela signifie que la mthode CreateCommand va, en fonction de la source de donnes, instancier un objet DbCommand avec une SqlCommand ou une OracleCommand. C'est exactement le mme fonctionnement que les classes que nous avions cres pour le framework 2.0, sauf que cette fois c'est dj prvu dans le framework. Passons tout de suite une explication plus pratique. Je vous ai dit juste avant que DbProviderFactories permet d'numrer les fournisseurs disponibles, en ralit, cette classe va lire les paramtres dfinis dans le fichier machine.config. Ce dernier contient dans la section <system.data> une balise <DbProviderFactories> qui dfinit un par un les fournisseurs installs. Exemple : <system.data> Bonnes pratiques pour les accs aux donnes par Johann Blais - 9 - Ce document est issu de http://www.developpez.com et reste la proprit exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise l'obtention pralable de l'autorisation de l'auteur. http://johannblais.developpez.com/tutoriel/dotnet/bonnes-pratiques-acces-donnees/ <DbProviderFactories> <add name="SqlClient Data Provider" invariant="System.Data.SqlClient" support="FF" description=".Net Framework Data Provider for SqlServer" type="System.Data.SqlClient.SqlClientFactory, System.Data, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> <add name="Odbc Data Provider" invariant="System.Data.Odbc" support="BF" description= ".Net Framework Data Provider for Odbc" type="System.Data.Odbc.OdbcFactory, System.Data, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> ... etc ... </DbProviderFactories> </system.data> Pour rcuprer cette liste, il suffit d'appeler la mthode DbProviderFactories.GetFactoryClasses() qui renvoie une DataTable contenant les donnes des fournisseurs. Cela vous permet de savoir dans votre application quelles sont les bibliothques d'accs aux bases de donnes disponibles l'excution. Vous pouvez ensuite instancier la factory associe au provider en utilisant la mthode DbProviderFactories.GetFactory(...), cette mthode prend en paramtre le nom du provider (System.Data.SqlClient par exemple) ou bien une DataRow issue du DataTable prcdemment cit. Une fois votre DbProviderFactory cr, vous pouvez utiliser ses mthodes CreateConnection, CreateCommand etc pour obtenir des instances des classes lies la source de donnes. Ceci n'est pas le plus utile pour la plupart des dveloppeurs. En ralit, cette fonctionnalit est surtout utile pour les dveloppeurs d'add-ins et de librairies orientes "accs aux donnes". Le plus important pour le dveloppeur est la possibilit d'utiliser les DbProviderFactory pour crire un code gnrique. Il permet de remplacer les interfaces utilises dans l'exemple prcdent par des classes abstraites. Le principal avantage est que les classes gnriques Db* permettent de grer tous les types d'accs aux donnes : mode connect et dconnect. Ce n'tait pas possible avec les interfaces IDb* car il n'existait pas de DataAdapter gnrique. Voici un exemple de ce quoi pourrait ressembler votre code : public class DataProvider { private static DbProviderFactory factory = DbProviderFactories.GetFactory("System.Data.SqlClient"); public static DbConnection CreateConnection() { DbConnection connection = factory.CreateConnection(); connection.ConnectionString = ConfigurationManager.ConnectionStrings["MyConnectionString"].ConnectionString; connection.Open(); return connection; } public static DbCommand CreateCommand(string commandText) { DbCommand command = CreateConnection().CreateCommand(); command.CommandText = commandText; return command; } Bonnes pratiques pour les accs aux donnes par Johann Blais - 10 - Ce document est issu de http://www.developpez.com et reste la proprit exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise l'obtention pralable de l'autorisation de l'auteur. http://johannblais.developpez.com/tutoriel/dotnet/bonnes-pratiques-acces-donnees/ public static DbDataReader CreateDataReader(string commandText) { return CreateCommand(commandText).ExecuteReader(System.Data.CommandBehavior.CloseConnection); } public static DbDataReader CreateDataReader(DbCommand command) { return command.ExecuteReader(System.Data.CommandBehavior.CloseConnection); } public static DataSet CreateDataSet(string commandText) { DbDataAdapter adapter = factory.CreateDataAdapter(); adapter.SelectCommand = CreateCommand(commandText); DataSet ds = new DataSet(); adapter.Fill(ds); adapter.SelectCommand.Connection.Close(); return ds; } } Ce code n'est qu'un trs simple exemple de ce qu'il est possible de faire avec les classes gnriques. A la diffrence de l'exemple en .NET 1.1, celui ci se focalise sur l'criture d'un code non dpendant de la source de donnes, alors que le premier exemple mettait l'accent sur l'utilisation de plusieurs sources de donnes de manire quasi transparente. En utilisant ces techniques, vous tes maintenant capables d'crire un code non dpendant de la source de donnes et/ou apte grer plusieurs sources de donnes de type diffrent en parallle. Passons maintenant une autre bonne pratique en ce qui concerne l'interrogation d'une base de donnes. Cette fois ci, cela ne concerne pas le type de la source de donnes mais plutt la manire de l'interroger. Bonnes pratiques pour les accs aux donnes par Johann Blais - 11 - Ce document est issu de http://www.developpez.com et reste la proprit exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise l'obtention pralable de l'autorisation de l'auteur. http://johannblais.developpez.com/tutoriel/dotnet/bonnes-pratiques-acces-donnees/ IV - Utilisation de requtes paramtres La plupart d'entre vous construit les requtes SQL en les concatnant morceau par morceau en insrant un morceau de SELECT par-ci, un morceau de FROM par-l et encore un bout de WHERE au passage. La plupart s'en satisfait ainsi, ne se posant pas la question de savoir s'il agit de la bonne mthode, et encore moins s'il en existe une autre. Et dans la plupart des cas, a fonctionne, enfin disons plutt, a ne plante pas trop. Alors vous avez peut-tre dj entendu quelqu'un parler de ce que l'on appelle les requtes paramtres (RP). Pour mettre les choses au point tout de suite, les RPs existent depuis longtemps, bien longtemps, alors que .NET n'tait pas prs de voir le jour. Il s'agit d'une fonctionnalit peu utilise voire mconnue de la plupart des gens (sauf les DBA, en considrant en plus que les DBA soient des gens, mais c'est un autre problme). IV-A - Le problme Avant d'tudier ce qu'est rellement une RP en .NET, attardons nous sur une requte classique crite par un dveloppeur lambda. L'objectif de la requte est d'extraire de la table commandes, les identifiants des commandes enregistres par un certain vendeur depuis une certaine date. Voici comment se prsente la requte dans la plupart des cas : SELECT id FROM commandes WHERE vendeur = [NOM] AND date > [DATE] Les valeurs entre crochets indiquent les endroits o il faut insrer une valeur spcifique. Ce qui donne dans la plupart des cas lors de la premire criture : string nom = "Georges"; DateTime date = DateTime.Today.AddDays(-7); IDbCommand maCommande = maConnexion.CreateCommand(); maCommand.CommandText = "SELECT id FROM command WHERE login=" + nom + " AND date > " + date.ToString("dd/MM/yyyy HH:mm:ss"); Premire excution de la requte, a explose. La raison, le format de la date, il y a un espace au milieu. Etant spcialiste SQL, vous ajoutez des apostrophes autour de la date, et vous transformez l'instruction en : string nom = "Georges"; DateTime date = DateTime.Today.AddDays(-7); IDbCommand maCommande = maConnexion.CreateCommand(); maCommand.CommandText = "SELECT id FROM command WHERE login='" + nom + "' AND date > '" + date.ToString("dd/MM/yyyy HH:mm:ss") + "'";
Deuxime excution, re-plantage, cette fois ci, c'est le format de la date qui est en cause. En effet le serveur est dans une culture anglo-saxone et le format de la date est mois/jour/anne. Qu' cela ne tienne : string nom = "Georges"; DateTime date = DateTime.Today.AddDays(-7); IDbCommand maCommande = maConnexion.CreateCommand(); maCommand.CommandText = "SELECT id FROM command WHERE login='" + nom + "' AND date > '" + date.ToString("yyyy-MM-dd") + "'";
Bonnes pratiques pour les accs aux donnes par Johann Blais - 12 - Ce document est issu de http://www.developpez.com et reste la proprit exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise l'obtention pralable de l'autorisation de l'auteur. http://johannblais.developpez.com/tutoriel/dotnet/bonnes-pratiques-acces-donnees/ Cette fois, la date fonctionne plus ou moins, mais a plante encore. Vous aurez remarqu que j'en ai profit pour changer le nom car, problme, tout le monde ne s'appelle pas Georges. Et dans ce cas, notre vendeur est Irlandais (pourquoi pas), et il s'appelle O'Hara. Vous vous doutez dj de ce qui s'est pass, l'apostrophe dans le nom a encore fait exploser la requte, nous avons une droit une belle erreur de syntaxe. Vous avez tous dj eu ce problme (pas les Irlandais, mais les apostrophes). Encore une fois, une nouvelle correction, vous tes toujours un pro de SQL et vous avez tout de suite pens "je vais 'chapper' les apostrophes". Soit : string nom = "O'Hara"; DateTime date = DateTime.Today.AddDays(-7); IDbCommand maCommande = maConnexion.CreateCommand(); maCommand.CommandText = "SELECT id FROM command WHERE login='" + nom.Replace("'", "''") + "' AND date > '" + date.ToString("yyyy-MM-dd") + "'"; Victoire, cette fois, a fonctionne. Enfin, j'ai envie de dire que a ne plante plus, et encore a me semble optimiste. Si vous avez un tant soit peu d'amour propre, vous avez honte de ce code, et je dois avouer que je vous comprends. Il doit srement y avoir une meilleure solution. Finalement, une utilit pourrait tre trouve pour ces RP. Vous avez eu raison de garder espoir. IV-B - La solution Concrtement, qu'est ce qu'une requte paramtre ? Il s'agit d'une requte qui contient des variables la place de certaines valeurs. Vous pouvez sans problme crire une requte paramtre dans votre diteur SQL prfr. Voici une requte exemple : SELECT id FROM commandes WHERE vendeur = @p_name AND date > @date_effet Cette requte extrait de la table commandes, les identifiants des commandes enregistres par un certain vendeur depuis une certaine date. Dans cet exemple, les deux valeurs pour le login et le mot de passe sont remplaces par deux paramtres identifis par l'@ qui les prcde. En fonction de votre navigateur SQL (TOAD, SQLNavigator, Entreprise Manager, SQL Server Management Studio Express, etc), vous serez peut-tre invits saisir des valeurs pour l'excution de la requte, dans le cas contraire, la requte va srement chouer car les paramtres sont en fait des variables qui doivent tre dclares plus haut dans le script (ceci n'est valable que pour l'excution dans un diteur SQL). Passons maintenant l'criture de ce type de requte en utilisant le framework .NET. L'criture se fait comme pour une requte traditionnelle : IDbCommand maCommande = maConnexion.CreateCommand(); maCommande.CommandType = CommandType.Text; maCommande.CommandText = "SELECT id FROM command WHERE login = @login AND date > @date_effet"; Il suffit ensuite de dclarer deux magnifiques paramtres SQL comme ceci : IDbParameter paramNom = maCommande.CreateParameter(); paramNom.Name = "@login"; paramNom.DbType = DbTypes.VarChar; paramNom.Direction = ParameterDirection.Input; paramNom.Value = "O'Hara"; IDbParameter paramDate = maCommande.CreateParameter(); paramDate.Name = "@date_effet"; Bonnes pratiques pour les accs aux donnes par Johann Blais - 13 - Ce document est issu de http://www.developpez.com et reste la proprit exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise l'obtention pralable de l'autorisation de l'auteur. http://johannblais.developpez.com/tutoriel/dotnet/bonnes-pratiques-acces-donnees/ paramDate.DbType = DbTypes.DateTime; paramDate.Direction = ParameterDirection.Input; paramDate.Value = DateTime.Today.AddDays(-7).Date; On termine par l'ajout de ces paramtres dans la liste des paramtres de la commande : maCommande.Parameters.Add(paramNom); maCommande.Parameters.Add(paramDate); Il ne reste plus qu' excuter votre requte de manire habituelle. Pour rsumer, nous venons de : a Crer une requte avec des "blancs", les paramtres, b Crer des paramtres SQL dont le nom correspond au nom d'un des paramtres, c Affecter un type et une valeur correspondante chaque paramtre, d Associer les paramtres notre requte, e Excuter la requte sur la source de donnes sans se ramasser un tas d'erreurs et sans se fatiguer, f Avoir honte en repensant toutes les bidouilles mises en place avant pour viter tous ces problmes. L'intrt des paramtres ne se manifeste pas seulement dans le scnario prcdemment dcrit. En effet les paramtres SQL ne sont pas un concept de dveloppement, mais un concept de base de donnes. Il existe des raisons supplmentaires d'utiliser des paramtres SQL qui sont cette fois plus lies la source de donnes. Pour cela intressons nous quelque peu la manire de travailler de nos SGBD prfrs (mme s'il existe des diffrences entre les diffrents produits, l'ide est la mme chaque fois). Lors de l'envoi d'une requte au SGBD, la requte est d'abord analyse pour en extraire les paramtres par exemple, mais aussi les index qui vont tre utiliss, etc. Ces donnes recueillies sont ensuite places (avec la requte) dans un cache interne. De cette manire, si la mme requte revient, il n'y aura pas ncessit de la r-analyser. Ce systme fonctionne bien SAUF dans le cas o toutes les requtes sont diffrentes, dans ce cas, le cache va grossir inutilement, les requtes vont tre analyses chaque fois. Vous tes en train de vous dire " oh mon dieu, c'est horrible ", et vous avez raison. Vous pensez aussi " Ouf, heureusement que j'utilise souvent la mme requte, je ne tombe jamais dans ce cas-l ", et l, c'est le drame, vous vous trompez lamentablement ! Car oui, vous tes en fait dans ce cas horrible qui fait peur au DBA. " Tu mens " me direz vous, et vous allez mme ajouter " quand je fais des insertions en masse, c'est le mme INSERT chaque fois ". Et bien non, je ne mens pas et je le prouve. Quand vous faites vos insertions, vous utilisez srement une requte de type : while (jAiDesDonnneesEnAttente) { IDbCommand maCommande = maConnexion.CreateCommand(); maCommande.CommandType = CommandType.Text; maCommande.CommandText = "INSERT INTO maTable VALUES('" + maDonneeQuiChangeAChaqueIteration + "')"; maCommande.ExecuteNonQuery(); } Au final, vous envoyez au SGBD des requtes du genre : "INSERT INTO maTable VALUES('TOTO')" "INSERT INTO maTable VALUES('NENESS')" "INSERT INTO maTable VALUES('JUNIOR')" Ce sont, vous l'avez maintenant compris, des requtes compltement diffrentes du point de vue du SGBD. Bonnes pratiques pour les accs aux donnes par Johann Blais - 14 - Ce document est issu de http://www.developpez.com et reste la proprit exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise l'obtention pralable de l'autorisation de l'auteur. http://johannblais.developpez.com/tutoriel/dotnet/bonnes-pratiques-acces-donnees/ Dans le cas prsent, si vous aviez utilis un paramtre SQL pour la valeur enregistrer, la requte aurait t la mme chaque fois, et uniquement la valeur du paramtre aurait t modifie. Vous pouviez mme crire tout le code de cration de la requte en dehors de la boucle pour ne laisser dans celle-ci que : while (jAiDesDonnneesEnAttente) { monParametre.Value = maDonneeQuiChangeAChaqueIteration; maCommande.ExecuteNonQuery(); } C'est finalement plus lisible/robuste/efficace que la premire mthode. Abordons maintenant un autre avantage inestimable des paramtres SQL, vous vous mettez l'abri des injections SQL. Il s'agit d'une attaque courante, principalement pour les applications Web, qui consiste introduire dans un champ de saisie, une chane reprsentant un morceau de requte SQL qui, si l'application n'est pas protge, va tre excut dans la source de donnes votre insu. Pour illustrer, reprenons le code prcdent en le modifiant un peu : IDbCommand maCommande = maConnexion.CreateCommand(); maCommande.CommandType = CommandType.Text; maCommande.CommandText = "INSERT INTO maTable VALUES('" + monTextBox.Text + "')"; maCommande.ExecuteNonQuery(); Les utilisateurs sympas vont saisir des chaines gentilles comme " bonjour " ou " mon PC il KC ". Ces utilisateurs l sont comme la piti dans l'oeil d'un percepteur d'impt, ils n'existent pas. Non la place, vous aurez des utilisateurs mal rass avec les cheveux longs (des mchants utilisateurs) qui vont essayer de saisir des chanes du style " '); DROP DATABASE; -- ". Au final, votre requte va donner : INSERT INTO maTable VALUES(''); DROP DATABASE; --') Au revoir la base de donnes, merci d'avoir jou. Si vous aviez utilis un paramtre SQL, la chane passe dans la requte aurait t 'chappe' et au final, vous auriez insr sans encombre la valeur saisie dans la zone de texte. J'en vois un paquet qui sont en train de paniquer, oui, effectivement, il est temps d'aller modifier vos applications avant qu'il ne soit trop tard. Voil pour la partie SQL Injection. Encore un autre avantage, en concatnant vos valeurs dans la requte, vous introduisez une source d'erreur, une couche d'illisibilit supplmentaire, car vous tes toujours oblig de compter les apostrophes, de savoir o il faut ajouter les plus, les virgules, les parenthses qui manquent, etc. En utilisant les paramtres, vous avez la possibilit de crer la requte et de la tester dans un outil externe (Entreprise Manager par exemple), et de la copier/coller en l'tat sans aucune modification dans votre code. Gain de temps en dveloppement et en maintenance future. Et un petit dernier pour la route, en concatnant la requte, vous passez ct de toute la belle gestion des erreurs du framework en abandonnant une hypothtique InvalidCastException en dbogage au 'profit' d'une espce d'erreur SQL gnrique qui ne vous donne aux mieux qu'une apprciation minable de la source de l'erreur. En effet en cas d'erreur sur un paramtre SQL, vous obtenez des exceptions types comme des InvalidCastException ou autre, alors qu'en utilisant une requte "concatne", vous n'avez que des exceptions gnriques... Pas terrible pour le dbogage, non ? Vous avez donc maintenant une solide connaissance de l'(immense) intrt des paramtres SQL. Il ne vous reste plus qu' mettre tout a en pratique. Bonnes pratiques pour les accs aux donnes par Johann Blais - 15 - Ce document est issu de http://www.developpez.com et reste la proprit exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise l'obtention pralable de l'autorisation de l'auteur. http://johannblais.developpez.com/tutoriel/dotnet/bonnes-pratiques-acces-donnees/ V - Conclusion Nous avons vu au travers de ces diffrents points qu'en matire d'accs au donnes, vous avez votre disposition toute une srie d'outils qui vous permettent de gagner du temps de dbogage et de maintenance. Il vous reste maintenant mettre en pratique tous ces concepts pour qu'ils deviennent des automatismes, car ce qui diffrencie un dveloppeur standard et un bon dveloppeur n'est pas la rapidit de dveloppement, mais le rapport entre la qualit du code et le temps de dveloppement. Bonnes pratiques pour les accs aux donnes par Johann Blais - 16 - Ce document est issu de http://www.developpez.com et reste la proprit exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise l'obtention pralable de l'autorisation de l'auteur. http://johannblais.developpez.com/tutoriel/dotnet/bonnes-pratiques-acces-donnees/ VI - Remerciements Je pourrais jamais assez remercier Aspic pour toute l'aide qu'il m'a apporte pour la rdaction de cet article. Un autre grand merci caro95470 pour la correction orthographique de cet article. Bonnes pratiques pour les accs aux donnes par Johann Blais - 17 - Ce document est issu de http://www.developpez.com et reste la proprit exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise l'obtention pralable de l'autorisation de l'auteur. http://johannblais.developpez.com/tutoriel/dotnet/bonnes-pratiques-acces-donnees/