Vous êtes sur la page 1sur 41

Utiliser OLEDB en Delphi

Optimisation des accs Base de donnes, IIIme Partie Par Franck SORIANO

Date de publication : 17 fvrier 2009 Dernire mise jour : 16 fvrier 2009

Cet article prsente comment utiliser directement OLEDB pour excuter une requte sur une base de donnes. SQL Server est utilis pour les exemples, mais ce tutoriel peut s'appliquer n'importe quel SGBD. Grce OLEDB et la classe TMemoryDataSet prsente dans l'article prcdent, on peut obtenir des performances quatre fois suprieures qu'avec une application ADO traditionnelle ou dbExpress. Commentez cet article :

Utiliser OLEDB en Delphi par Franck SORIANO

I - Introduction..............................................................................................................................................................3 Tlcharger les sources de l'article....................................................................................................................... 3 I-B - Organisation du codes source....................................................................................................................... 3 II - Mise en oeuvre de OLEDB................................................................................................................................... 4 II-A - Connexion/Dconnexion............................................................................................................................... 4 II-A-1 - La classe TOleDbConnection...............................................................................................................4 II-A-2 - Ouvrir la connexion : DoConnect......................................................................................................... 4 II-A-3 - Deconnexion : DoDisconnect............................................................................................................... 7 II-A-4 - Savoir si on est connect : GetConnected.......................................................................................... 7 II-A-5 - Comment construire une chane de connexion ?................................................................................ 8 II-B - Gestion des Erreurs OLEDB et messages d'informations............................................................................9 II-C - Excuter une requte ne renvoyant aucun rsultat................................................................................... 14 II-D - Excuter une requte de type SELECT..................................................................................................... 16 II-D-1 - Lecture de la structure du jeu de donne : Describe.........................................................................18 Remarque sur les types de donnes utiliss............................................................................................21 II-D-2 - Chargement des donnes : FetchAll..................................................................................................21 II-D-2-a - Dfinition du Binding..................................................................................................................21 II-D-2-b - Lecture des donnes................................................................................................................. 25 II-E - Requtes paramtres................................................................................................................................28 II-F - Prparer les Requtes................................................................................................................................ 33 II-G - Gestion des Transactions........................................................................................................................... 34 II-G-1 - Dmarrer une transaction : StartTransaction..................................................................................... 34 II-G-2 - Valider une transaction : Commit....................................................................................................... 35 II-G-3 - Annuler une transaction : Abort......................................................................................................... 36 III - Evaluation des rsultats et exemples d'utilisations............................................................................................ 37 III-A - Environnement de tests............................................................................................................................. 37 III-B - Connexion/Dconnexion............................................................................................................................ 37 III-C - Excuter une requte................................................................................................................................ 39 III-D - Excuter une requte paramtre.............................................................................................................40 III-E - Messages d'informations............................................................................................................................40 III-F - Lire un fichier EXCEL................................................................................................................................ 41 IV - Conclusion.......................................................................................................................................................... 41 V - Rfrences...........................................................................................................................................................41 VI - Remerciements................................................................................................................................................... 41

-2Les sources prsentes sur cette page sont libres de droits et vous pouvez les utiliser votre convenance. Par contre, la page de prsentation constitue une uvre intellectuelle protge par les droits d'auteur. Copyright 2009 Franck SORIANO. Aucune reproduction, mme 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 intrts. Cette page est dpose la SACD.

http://fsoriano.developpez.com/articles/db/oledb/delphi/

Utiliser OLEDB en Delphi par Franck SORIANO

I - Introduction
OLEDB a t conu pour remplacer ODBC et uniformiser les accs aux bases de donnes. En fait, OLEDB va bien au del et permet d'accder toute source de donnes pour laquelle on dispose d'un provider OLEDB. Il peut s'agir d'un SGBD, mais galement d'un fichier Excel... OLEDB est une API bas niveau, conue pour donner les performances optimales. Cependant, ces performances viennent au dtriment de la facilit d'utilisation. Aussi, Microsoft a dfini ADO par dessus OLEDB afin de simplifier son usage. Dans Delphi, cette couche ADO est elle-mme encapsule dans les composants dbGO. Or comme on a pu le voir avec le comparatif sur les API d'accs aux donnes, cet empilement de couches dgrade les performances de faon significative. Pour retrouver les performances originelles, Il faut appeler OLEDB directement, en court-circuitant la couche ADO et l'encapsulation dbGO. Dans cet article, nous allons voir les principes d'utilisation d'OLEDB. Nous allons dvelopper nos propres composants d'accs aux donnes afin d'utiliser facilement OLEDB dans nos applications Delphi. Nous allons effectuer tous les dveloppements avec SQL Server 2005. Cependant, OLEDB tant une API gnrique, rien n'interdit d'ouvrir la connexion sur un autre SGBD...

Tlcharger les sources de l'article


Tout au long de cet article, nous allons dvelopper un jeu de composants pour encapsuler les appels OLEDB. Dans ce tutoriel nous allons voir les bases de OLEDB et crire la classe TOleDbConnection afin de grer la connexion OLEDB. Nous allons galement dvelopper la classe TOleDbDataSet pour lire et manipuler le rsultat d'une requte. Les dveloppements s'appuieront sur les lments suivants : ETW : Pour tracer les requtes SQL et bnficier d'un profiler SQL. TMemoryDataSet : Pour mmoriser et manipuler les resultats d'une requte OLEDB. Emplacement ftp://ftp-developpez.com/fsoriano/archives/etw/delphi/fichiers/etw-sources.zip ./fichiers/oledb-sources.zip ./fichiers/oledb.docx

Description ETW OLEDB L'article au format docx

L'ensemble des codes sources compile avec Turbo Delphi Explorer.

I-B - Organisation du codes source


Dans le prochain tutoriel, nous verrons comment utiliser OLEDB pour remplir une table SQL trs rapidement grce au chargement en blocs de SQL Server (L'quivalent de DTS, ou de la classe SqlBulkCopy d'ADO.NET). Aussi, pour les besoins des deux tutoriels, les sources sont organises selon une hirarchie deux niveaux : Les classes TCustomOleDbConnection et TCustomOleDbDataset implmentent les fonctionnalits OLEDB communes pour les deux articles. Ensuite, les classes TOleDbConnection et TOleDbDataSet drivent des deux prcdentes pour implmenter les mthodes spcifiques pour ce tutoriel.

-3Les sources prsentes sur cette page sont libres de droits et vous pouvez les utiliser votre convenance. Par contre, la page de prsentation constitue une uvre intellectuelle protge par les droits d'auteur. Copyright 2009 Franck SORIANO. Aucune reproduction, mme 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 intrts. Cette page est dpose la SACD.

http://fsoriano.developpez.com/articles/db/oledb/delphi/

Utiliser OLEDB en Delphi par Franck SORIANO

II - Mise en oeuvre de OLEDB II-A - Connexion/Dconnexion


Concrtement, OLEDB est une librairie d'objets COM. Pour l'utiliser il suffit donc d'importer la TLB dans Delphi, puis d'instancier les objets dont on a besoin comme pour n'importe quel objet COM. En fait, c'est encore plus simple. L'unit d'import d'OLEDB est dj fournie en standard dans Delphi : il s'agit de l'unit OleDb.pas que l'on peut utiliser directement. OLEDB est une API gnrique, indpendante du SGBD. Cette indpendance est obtenue grce aux providers OLEDB. Chaque SGBD doit fournir un provider OLEDB qui respecte les spcifications OLEDB. Dans nos applications clientes, on se contente d'instancier le provider correspondant nos besoins, puis de l'utiliser par l'intermdiaire de diffrentes interfaces. C'est exactement le mme principe que pour ADO.NET.

II-A-1 - La classe TOleDbConnection


Nous devons commencer par dfinir une classe drive de TCustomConnection pour grer la connexion OLEDB. Cette dernire est relativement simple crire. En fait, il suffit de surcharger les mthodes suivantes : DoConnect : Cette mthode est appele automatiquement par TCustomConnection pour ouvrir la connexion la base. DoDisconnect : Cette mthode est appele par TCustomConnection pour fermer la connexion. GetConnected : Cette fonction renvoie True ou False pour indiquer si la connexion est ouverte.

II-A-2 - Ouvrir la connexion : DoConnect


Tout commence donc par l'instanciation d'un provider. Sur le principe, il s'agit d'un simple objet COM. Il suffit donc de connatre son CLSID pour l'instancier. Pour travailler avec SQL Server, on a le choix entre diffrents providers : Microsoft OLEDB provider for SQL Server : C'est le provider historique disponible depuis les dbuts d'OLEDB et ADO. Ce dernier ne permet pas d'exploiter les dernires nouveauts de SQL 2005. Microsoft OLEDB provider for ODBC Drivers : C'est en fait une couche d'adaptation qui permet OLEDB de travailler avec un driver ODBC. Cette solution permet d'utiliser OLEDB lorsqu'on ne dispose pas d'un provider OLEDB appropri. SQL Native client : Son nom n'est pas trs explicite, mais il s'agit bel et bien du nouveau provider OLEDB recommand pour accder une instance SQL 2005. C'est lui que nous allons utiliser.

Son CLSID est le suivant :


CLSID_SQLOLEDB : TGUID = '{0C7FF16C-38E3-11d0-97AB-00C04FC2AD98}';

On peut ainsi instancier l'objet COM. Ce dernier retourne une interface IDBInitialize permettant d'ouvrir la connexion. Cependant, avant d'ouvrir la connexion, il faut commencer par dfinir les proprits de l'objet (Login, password, nom du serveur...). Les proprits initialiser, ainsi que les valeurs dfinir sont spcifiques chaque provider. Si on garde cette approche, le code va vite devenir difficilement maintenable. La procdure de connexion est spcifique au provider, donc au SGBD. Pour une API gnrique, ce n'est pas ce qu'il y a de mieux.

-4Les sources prsentes sur cette page sont libres de droits et vous pouvez les utiliser votre convenance. Par contre, la page de prsentation constitue une uvre intellectuelle protge par les droits d'auteur. Copyright 2009 Franck SORIANO. Aucune reproduction, mme 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 intrts. Cette page est dpose la SACD.

http://fsoriano.developpez.com/articles/db/oledb/delphi/

Utiliser OLEDB en Delphi par Franck SORIANO

Pour simplifier le problme, OLEDB dfinie et implmente un service indpendant des providers. Il s'agit du service DataLink. Ce dernier n'est rien d'autre qu'une Factory pour les providers. Il permet d'instancier et d'initialiser n'importe quel provider partir d'une chane de connexion, comme pour ADO. L'ouverture de la connexion est alors grandement facilite puisque tout est dfini dans la chane de connexion : On commence par instancier un objet CLSID_MSDAINITIALIZE. Il s'agit du service OleDb pour le Data link. Ce dernier implmente l'interface IDataInitialize. Elle existe dans deux versions pour Delphi, une version qui utilise la convention SafeCall (IDataInitializeSC), et une version avec la convention StdCall. La version SafeCall gre automatiquement les codes de retour HResult. C'est elle que nous utilisons :
var FDataInitialize : IDataInitializeSC; ... OleCheck(CoCreateInstance(CLSID_MSDAINITIALIZE, nil, CLSCTX_INPROC_SERVER, IID_IDataInitialize, FDataInitialize));

Lorsqu'on dispose d'IDataInitialize, il n'y a plus qu' demander l'objet DataSouce (le provider) dfinit dans la chane de connexion. C'est ce qu'on fait en appelant la mthode GetDataSource.
var Unknown : IUnknown; FDbINitialize : IDbInitialize; ... FDataInitialize.GetDataSource(nil, CLSCTX_INPROC_SERVER, PWideChar(FConnectionString), IID_IDBInitialize, Unknown); FDbInitialize := Unknown as IDBInitialize;

On obtient alors une interface IDbInitialize, qui rfrence un objet DataSource instanci et pr-initialis. Cependant, la connexion la base n'est pas ouverte pour autant. Il faut encore l'initialiser en appelant Initialize, et ouvrir une session avec la mthode CreateSession de l'interface IDBCreateSession :
// On ouvre la connexion sur la source de donne. OleDbCheck(FDbInitialize.Initialize); // Il ne reste plus qu' crer une session par dfaut. FSession := nil; OleDbCheck((FDbInitialize as IDBCreateSession).CreateSession(nil, IID_IOpenRowset, FSession));

Rassemblons le tout dans la mthode DoConnect. On en profite au passage pour tracer l'ouverture de la connexion avec ETW.
procedure TOleDbConnection.DoConnect; var Unknown : IUnknown; FDataInitialize : IDataInitializeSC; begin // Tout d'abord, la chane de connexion doit avoir t dfinie if FConnectionString = '' then raise EOleDbException.Create('La chane de connexion n''est pas dfinie !'); SQLLogger.Trace(EVENT_SQL_INFO, 'Ouverture de la connexion : ' + FConnectionString, TRACE_LEVEL_INFORMATION); try // Enfin, on a besoin d'un accs IMAlloc pour grer certaines allocation // de mmoire. OleCheck(CoGetMalloc(1, FMAlloc)); // L'initialisation de la connexion est faite indirectement, // par l'intermdiaire du service OLEDB grant les chanes de connexion. // On commence donc par se connecter ce service, en instanciant un // objet CLSID_MSDAINITIALIZE. OleCheck(CoCreateInstance(CLSID_MSDAINITIALIZE, nil, CLSCTX_INPROC_SERVER, IID_IDataInitialize, FDataInitialize));

-5Les sources prsentes sur cette page sont libres de droits et vous pouvez les utiliser votre convenance. Par contre, la page de prsentation constitue une uvre intellectuelle protge par les droits d'auteur. Copyright 2009 Franck SORIANO. Aucune reproduction, mme 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 intrts. Cette page est dpose la SACD.

http://fsoriano.developpez.com/articles/db/oledb/delphi/

Utiliser OLEDB en Delphi par Franck SORIANO

// Dans un deuxime temps, on cre l'objet DataSource oledb partir // de la chane de connexion. L'objet ainsi cr est dj initialis. // Il ne restera plus qu' ouvrir la connexion. Unknown := nil; FDataInitialize.GetDataSource(nil, CLSCTX_INPROC_SERVER, PWideChar(FConnectionString), IID_IDBInitialize, Unknown); FDbInitialize := Unknown as IDBInitialize; FDataInitialize := nil; // On n'a plus besoin d'accder au service ! // On ouvre la connexion sur la source de donne. OleDbCheck(FDbInitialize.Initialize); // Il ne reste plus qu' crer une session par dfaut. FSession := nil; OleDbCheck((FDbInitialize as IDBCreateSession).CreateSession(nil, IID_IOpenRowset, FSession)); // On essaie d'obtenir l'interface ITransactionLocal pour la gestion des // transaction. Cependant, il se peut que le provider OLEDB ne gre pas les // transaction et n'implmente pas l'interface ITransactionLocal. if FSession.QueryInterface(IID_ITransactionLocal, unknown) = S_OK then FTransaction := unknown as ITransactionLocal else FTransaction := nil; // La connexion a t effectue. SQLLogger.TraceConnect; except on e:exception do begin // En cas d'erreur la connexion, on trace l'exception. SQLLogger.TraceException(e); // Puis on nettoie les interfaces. FSession := nil; // La connexion a chou. FDbInitialize := nil; FDataInitialize := nil; // On redclenche l'exception. raise; end; end; end;

Remarque : DoConnect fait galement appel une mthode OleDbCheck. Cette dernire est utilise pour la gestion des erreurs et sera explique plus loin.

-6Les sources prsentes sur cette page sont libres de droits et vous pouvez les utiliser votre convenance. Par contre, la page de prsentation constitue une uvre intellectuelle protge par les droits d'auteur. Copyright 2009 Franck SORIANO. Aucune reproduction, mme 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 intrts. Cette page est dpose la SACD.

http://fsoriano.developpez.com/articles/db/oledb/delphi/

Utiliser OLEDB en Delphi par Franck SORIANO

II-A-3 - Deconnexion : DoDisconnect


La dconnexion quant elle est vraiment trs simple implmenter. Il suffit d'appeler la mthode Uninitialize de l'interface IDBInitialize obtenue au moment de l'ouverture de la connexion. Cependant, avant de l'appeler il faut veiller librer toutes les rfrences aux autres objets OLEDB utiliss par la connexion. En particulier l'objet session. On implmente DoDisconnect de la faon suivante :
// Est appele par TCustomConnection pour fermer la connexion. procedure TCustomOleDbConnection.DoDisconnect; begin SQLLogger.Trace(EVENT_SQL_INFO, 'Fermeture de la connexion : ' + FConnectionString, TRACE_LEVEL_INFORMATION); try FTransaction := nil; // Il faut commencer par librer l'objet Session. FSession := nil; // On ferme la connexion la source de donnes. OleDbCheck(FDbInitialize.Uninitialize); // Puis il ne reste plus qu' librer les interfaces. FDbInitialize := nil; // Enfin, on trace la dconnexion. SQLLogger.TraceDisconnect; except on e:exception do begin // En cas d'erreur la connexion, on trace l'exception. SQLLogger.TraceException(e); raise; // Et on redclenche l'exception. end; end; end;

II-A-4 - Savoir si on est connect : GetConnected


Dans notre mcanisme de connexion, on demande une rfrence IDbInitialize, et on cre une session avec IDBCreateSession.CreateSession. Lors de la dconnexion, on libre ces rfrences. Aussi GetConnected va simplement se contenter de regarder si la rfrence de la session est dfinie :
// Indique si la connexion est ouverte. function TCustomOleDbConnection.GetConnected: Boolean; begin result := Assigned(FSession); end;

-7Les sources prsentes sur cette page sont libres de droits et vous pouvez les utiliser votre convenance. Par contre, la page de prsentation constitue une uvre intellectuelle protge par les droits d'auteur. Copyright 2009 Franck SORIANO. Aucune reproduction, mme 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 intrts. Cette page est dpose la SACD.

http://fsoriano.developpez.com/articles/db/oledb/delphi/

Utiliser OLEDB en Delphi par Franck SORIANO

II-A-5 - Comment construire une chane de connexion ?


On a vu qu'on pouvait ouvrir une connexion et la configurer grce une chane de connexion. Maintenant, il serait intressant d'avoir une bote de dialogue pour construire automatiquement cette chane, comme pour ADO. OLEDB permet de le faire avec les interfaces IDBPromptInitialize et IDataInitialize. IDBPromptInitialize permet d'afficher la bote de dialogue et retourne un objet provider pr-initialis. IDataInitialise possde une mthode qui retourne la chane de connexion correspondant un objet initialis. Il suffit donc d'appeler les deux successivement :
// Affiche la bote de dialogue permettant de construire la chane de connexion. // La mthode renvoie la chane de connexion ainsi construite si l'utilisateur // valide la slection. // Autrement, la mthode retourne une chane vide. // <ParenthWnd> peut tre utilis pour indiquer le Handle de la fentre parente // de la bote de dialogue. class function TOleDbConnection.PromptConnexionString( OldConnexionString : widestring =''; ParenthWnd : cardinal = 0) : widestring; var DBPromptInitialize : IDBPromptInitialize; DBInitialize : IUnknown; DataInitialize : IDataInitialize; hResult : integer; CntStr : PWideChar; begin // On instancie les services OLEDB. OleCheck(CoCreateInstance(CLSID_DATALINKS, nil, CLSCTX_INPROC_SERVER, IID_IDBPromptInitialize, DBPromptInitialize)); OleCheck(CoCreateInstance(CLSID_MSDAINITIALIZE, nil, CLSCTX_INPROC_SERVER, IID_IDataInitialize, DataInitialize)); if OldConnexionString<>'' then begin // Si une chaine de connexion a t fournie, on va l'utiliser pour initialiser // la bote de dialogue. // Pour cel, on cre l'objet partir de la chane de connexion fournie. DBInitialize := nil; DataInitialize.GetDataSource(nil, CLSCTX_INPROC_SERVER, PWideChar(OldConnexionString), IID_IDBInitialize, DBInitialize); end else DBInitialize := nil; // Puis on appelle le dialogue prdfini. // Si une chane de connexion initiale a t fournie, elle a permis d'initialiser // DBInitialize. PromptDataSource va alors s'en servir pour prinitialiser // la bote de dialogue. hResult := DBPromptInitialize.PromptDataSource(nil, ParenthWnd, // Handle de la fentre parent DBPROMPTOPTIONS_PROPERTYSHEET, // Paramtrage de la connexion 0, // Pas de filtre sur les providers autoriss. nil, // Pas de filtre sur les providers autoriss. nil, // Pas de filtre sur les providers autoriss. IID_IDBInitialize, // IID de l'interface retourner. DBInitialize); // Interface de retour. case hResult of S_OK: // L'utilisateur a valid la slection begin // On appelle GetInitializationString pour connaitre la chane de connexion OleCheck(DataInitialize.GetInitializationString(DBInitialize, true, CntStr)); result := cntStr; end; DB_E_CANCELED: // L'utilisateur a annul la bote de dialogue. result := ''; else OleCheck(hResult); end; end;
-8Les sources prsentes sur cette page sont libres de droits et vous pouvez les utiliser votre convenance. Par contre, la page de prsentation constitue une uvre intellectuelle protge par les droits d'auteur. Copyright 2009 Franck SORIANO. Aucune reproduction, mme 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 intrts. Cette page est dpose la SACD.

http://fsoriano.developpez.com/articles/db/oledb/delphi/

Utiliser OLEDB en Delphi par Franck SORIANO

A l'excution, le rsultat est le suivant :

Ca vous rappelle quelque chose ? Et oui, c'est exactement la bote de dialogue ADO. En fait, ADO n'tant qu'une surcouche par-dessus OLEDB, c'est ADO qui utilise le dialogue OLEDB.

II-B - Gestion des Erreurs OLEDB et messages d'informations


OLEDB est une librairie d'objets COM. Ces derniers vont donc naturellement s'appuyer sur le mcanisme des exceptions COM pour remonter les erreurs. Ainsi, si une erreur survient lors de l'appel d'une mthode, cette dernire va dclencher une exception COM, qui remontera l'appelant par l'intermdiaire du HResult. Si on utilise la convention d'appel safecall dans les interfaces, Delphi va automatiquement tester les hresult notre place, et dclencher les exceptions pour nous. On pourrait donc se contenter de ce principe dans un premier temps. Cependant, OLEDB utilise un dispositif plus riche pour remonter les erreurs. Lorsqu'on appelle une mthode, si cette dernire choue, au lieu de remonter une seule erreur, OLEDB remonte une liste d'erreurs selon un mcanisme spcial. Avec la gestion standard des exceptions COM, on ne rcupre que la premire exception de la liste. Or bien souvent, cette dernire se contente de dire que l'appel a chou, et c'est la suivante qui explique la cause de l'erreur. Par exemple, si on essaie d'excuter une requte SQL qui contient une erreur de syntaxe, OLEDB va retourner une erreur du style : "Erreur lors de la prparation de l'instruction". Il faut aller fouiller dans la liste des erreurs pour voir que le Prepare a chou parce que le SGBD a retourn une erreur : "erreur de syntaxe ligne ... colonne ...". Voyons donc comment rcuprer la liste complte des erreurs :

-9Les sources prsentes sur cette page sont libres de droits et vous pouvez les utiliser votre convenance. Par contre, la page de prsentation constitue une uvre intellectuelle protge par les droits d'auteur. Copyright 2009 Franck SORIANO. Aucune reproduction, mme 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 intrts. Cette page est dpose la SACD.

http://fsoriano.developpez.com/articles/db/oledb/delphi/

Utiliser OLEDB en Delphi par Franck SORIANO

Tout d'abord, nous devons faire la gestion des erreurs nous mme, et ne pas utiliser la gestion par dfaut faite par Delphi. Cela implique qu'on ne doit utiliser que les interfaces dfinies avec la convention d'appel stdcall. Chaque appel d'une mthode d'un objet COM retourne un code de retour : Le HResult. Ce dernier est encod d'une faon particulire. Le bit de poids fort est positionn 1 pour indiquer une erreur et 0 en cas de succs. Delphi fournit la fonction Succeeded pour tester si un Hresult donn indique une erreur. En cas d'erreur, il faut appeler la fonction GetErrorInfo pour obtenir les informations relatives l'erreur. Cette fonction renvoie une interface IErrorInfo avec le code et le message d'erreur. Ca c'est la gestion standard des exceptions COM. Comme on peut le voir, il n'est pas possible d'obtenir une liste d'erreur de cette manire. Avec OLEDB, une fois qu'on a obtenu l'interface IErrorInfo, il suffit en fait de s'en servir pour obtenir l'interface IErrorRecords. Cette dernire permet tout simplement d'obtenir une liste d'erreurs IErrorInfo. Cependant IErrorInfo peut ne pas tre suffisant pour dcrire compltement une erreur. C'est pourquoi OLEDB prvoit galement la mthode GetCustomErrorObject pour que chaque provider puisse fournir sa propre description de l'erreur. A ce titre, les providers SQL Server dfinissent l'interface ISQLServerErrorInfo. Cette dernire sert identifier le message d'erreur, mais aussi le numro de l'erreur, le numro de la ligne o elle s'est dclenche, ainsi que la gravit de l'erreur. Pour SQL Server, le niveau de gravit est trs important. En effet, une commande SQL peut retourner des erreurs, mais galement des messages d'information qui ne sont pas des erreurs, mais qui sont malgr tout renvoys sous la forme d'une erreur de gravit faible. Par exemple, si on fait un PRINT dans un script, le message du script remonte dans OLEDB sous la forme d'une erreur de gravit 0. Pour que les choses soient encore un peu plus complexes, pour un PRINT, le message remonte sous la forme d'une erreur, mais le hResult de l'appel ayant dclench le PRINT indique lui que tout s'est bien pass... De plus, un mme appel peut trs bien retourner la fois des messages d'informations et des erreurs. Ainsi, pour grer correctement les erreurs avec SQL Server, il faut ignorer la valeur du hResult et toujours essayer de lire la description complte des erreurs. Pour chaque erreur, il faut tester le niveau de gravit pour savoir s'il s'agit d'une erreur ou d'un message d'information. C'est la gestion que nous allons implmenter avec la mthode OleDbCheck.
procedure TCustomOleDbConnection.OleDbCheck(hResult : integer); var ErrInfo : IErrorInfo; ErrorInfo2 : IErrorInfo; Errors : IErrorRecords; nbError : cardinal; i : integer; Description : WideString; ErrMsg : widestring; ProcedureName : widestring; msg : string; unknown : IUnknown; SQLServerErrorInfo : ISQLServerErrorInfo; SQLServerError : PSSERRORINFO; SQLServerErrorMessage : PWideChar; begin ErrMsg := ''; // Premirement, on regarde si des informations sont disponibles sur l'erreur en cours. GetErrorInfo(0, ErrInfo); if Assigned(ErrInfo) // On a russit obtenir des informations. then begin // OLEDB tend la gestion des exceptions COM de faon retourner une liste d'exceptions // (la liste des exceptions l'origine de l'erreur) au lieu d'une seule et unique erreur. // Si on veut connaitre la cause de l'erreur, il faut lire la liste complte. Errors := ErrInfo as IErrorRecords;

- 10 Les sources prsentes sur cette page sont libres de droits et vous pouvez les utiliser votre convenance. Par contre, la page de prsentation constitue une uvre intellectuelle protge par les droits d'auteur. Copyright 2009 Franck SORIANO. Aucune reproduction, mme 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 intrts. Cette page est dpose la SACD.

http://fsoriano.developpez.com/articles/db/oledb/delphi/

Utiliser OLEDB en Delphi par Franck SORIANO

// Maintenant, il ne reste plus qu' parcourir la liste des erreurs. Errors.GetRecordCount(nbError); for i := 0 to nbError-1 do begin // Si on travaille avec SQL Server, on peut obtenir des informations plus compltes sur // les erreurs avec l'interface ISQLServerErrorInfo. On commence donc par rechercher si // ces informations sont disponibles. Si le provider OLEDB n'est pas un provider SQL Server, // l'interface ISQLServerErrorInfo ne sera pas disponible. unknown := nil; Errors.GetCustomErrorObject(i, IID_ISQLServerErrorInfo, unknown); if Assigned(unknown) then begin SQLServerErrorInfo := unknown as ISQLServerErrorInfo; SQLServerError := nil; SQLServerErrorMessage := nil; if SQLServerErrorInfo.GetErrorInfo(SQLServerError, SQLServerErrorMessage) = S_OK then begin if Assigned(SQLServerError) then begin try // A prsent, on construit le message d'erreur partir des informations : // Le champ bClass indique le niveau de gravit de l'erreur. Les valeurs // infrieures 10 indiquent qu'il s'agit d'un simple message d'information. // Les valeurs suprieures 10 dsignent les erreurs. if SQLServerError.bClass <= 10 // Il s'agit d'un message d'information then begin // On ajoute le message la liste des messages d'information. InfoMessages.Add(SQLServerError.pwszMessage); end else begin // Il s'agit d'une erreur. On met en forme le message d'erreur dans msg. msg := 'MSG-' + IntToStr(SQLServerError.lNative) + ', '; // Numro de l'erreur // Traitement du nom de la procdure stocke l'origine de l'erreur ProcedureName := SQLServerError.pwszProcedure; if ProcedureName<>'' then msg := ProcedureName + ', '; // Ajout du numro de ligne de l'erreur msg := msg + 'Line ' + IntToStr(SQLServerError.wLineNumber) + ', '; // Enfin, on termine avec le message de l'erreur. msg := msg + SQLServerError.pwszMessage; // Il s'agit d'une erreur. On l'ajoute au message complet des erreurs. ErrMsg := ErrMsg + msg + #13#10; end; finally if Assigned(SQLServerError) then FMAlloc.Free(SQLServerError); if Assigned(SQLServerErrorMessage) then FMAlloc.Free(SQLServerErrorMessage); end; end; end; end else begin // Il ne s'agit pas d'une erreur SQLServer, on traite l'erreur de faon classique. // On commence par lire l'erreur ni dans ErrorInfo2 OleCheck(Errors.GetErrorInfo(i, GetSystemDefaultLCID, ErrorInfo2)); OleCheck(ErrorInfo2.GetDescription(Description)); ErrMsg := ErrMsg + Description + #13#10; end; end; end; // // // if Enfin, il ne reste plus qu' dclencher l'exception en cas d'erreur. Normalement, s'il y a eu une erreur, hResult doit contenir un code d'erreur et ErrMsg la description de l'erreur. (ErrMsg<>'') or (not Succeeded(hResult))

- 11 Les sources prsentes sur cette page sont libres de droits et vous pouvez les utiliser votre convenance. Par contre, la page de prsentation constitue une uvre intellectuelle protge par les droits d'auteur. Copyright 2009 Franck SORIANO. Aucune reproduction, mme 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 intrts. Cette page est dpose la SACD.

http://fsoriano.developpez.com/articles/db/oledb/delphi/

Utiliser OLEDB en Delphi par Franck SORIANO

then begin if ErrMsg<>'' then raise EOleDbException.Create(ErrMsg) // On dclenche une erreur spcifique OLEDB else raise EOleSysError.Create('', hResult, 0); // On dclenche une erreur systme OLE end; end;

L'interface IErrorRecords est spcifie par OLEDB. Cependant l'unit OleDb de Delphi ne contient pas sa dclaration (probablement parce que cette dernire n'est pas compatible OLE Automation). Nous devons donc la dclarer nous mme :
type // L'interface IErrorRecords fait parti de OLEDB. Cependant l'unit OleDb.pas ne la dclare // pas. On doit donc la dclarer nous-mme. IErrorRecords = interface(IUnknown) ['{0c733a67-2a1c-11ce-ade5-00aa0044773d}'] function AddErrorRecord(pErrorInfo : PErrorInfo; dwLookupID : cardinal; pdispparams : pointer; punkCustomError : IUnknown; dwDynamicErrorID : cardinal) : HResult; stdcall; function GetBasicErrorInfo(ulRecordNum : cardinal; pErrorInfo : PErrorInfo) : HResult; stdcall; function GetCustomErrorObject(ulRecordNum : cardinal; const riid : TGUID; var ppObject : IUnknown) : HResult; stdcall; function GetErrorInfo(ulRecordNum : cardinal; lcid : cardinal; var ppErrorInfo : IErrorInfo) : HResult; stdcall; function GetErrorParameters(ulRecordNum : cardinal; pdispparams : pointer) : HResult; stdcall; function GetRecordCount(var pcRecords : cardinal) : HResult; stdcall; end;

De mme l'interface ISQLServerErrorInfo n'est dfinie que dans le fichier entte de SQL Native Client. On doit donc galement la dclarer nous mme :
ISQLServerErrorInfo = interface(IUnknown) ['{5CF4CA12-EF21-11d0-97E7-00C04FC2AD98}'] function GetErrorInfo(out ppErrorInfo : PSSERRORINFO; out Error : PWideChar) : Hresult; stdcall; end;

- 12 Les sources prsentes sur cette page sont libres de droits et vous pouvez les utiliser votre convenance. Par contre, la page de prsentation constitue une uvre intellectuelle protge par les droits d'auteur. Copyright 2009 Franck SORIANO. Aucune reproduction, mme 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 intrts. Cette page est dpose la SACD.

http://fsoriano.developpez.com/articles/db/oledb/delphi/

Utiliser OLEDB en Delphi par Franck SORIANO

La mthode GetErrorInfo retourne une structure SSERRORINFO dfinie de la faon suivante :


// Enregistrement relatif une erreur SQL Server PSSERRORINFO = ^SSERRORINFO; SSERRORINFO = packed record pwszMessage : PWideChar; pwszServer : PWideChar; pwszProcedure : PWideChar; lNative : cardinal; bState : byte; bClass : byte; wLineNumber : word; end;

Lors des appels aux mthodes OLEDB, il ne restera plus qu'a tester chaque code de retour avec OleDbCheck. En cas d'erreur, OleDbCheck dclenche une exception avec la description complte de l'erreur. Les messages d'informations sont filtrs et ajouts la fin de la liste InfoMessages de TOleDbConnection. Cette liste sera vide chaque fois qu'on excute une nouvelle requte avec OpenSQL ou ExecSQL. Au final, OleDbCheck est capable de traiter n'importe quelle erreur issue d'un provider OLEDB quelconque. En revanche, elle a t enrichie pour traiter galement les spcificits de SQL Server. Si on veut faire un composant spcialis pour un autre provider, on devra srement traiter d'autres interfaces tendues pour la gestion des erreurs. Dans ce cas, il sera sans doute intressant de rendre la mthode OleDbCheck virtuelle, afin qu'elle puisse tre surcharge dans un composant spcialis.

- 13 Les sources prsentes sur cette page sont libres de droits et vous pouvez les utiliser votre convenance. Par contre, la page de prsentation constitue une uvre intellectuelle protge par les droits d'auteur. Copyright 2009 Franck SORIANO. Aucune reproduction, mme 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 intrts. Cette page est dpose la SACD.

http://fsoriano.developpez.com/articles/db/oledb/delphi/

Utiliser OLEDB en Delphi par Franck SORIANO

II-C - Excuter une requte ne renvoyant aucun rsultat


Nous avons vu comment nous connecter la base, et comment traiter les erreurs. Maintenant on va pouvoir commencer entrer dans le vif du sujet. Voyons tout d'abord comment excuter une requte simple qui ne retourne aucun rsultat (nous verrons comment lire les donnes retournes plus tard) et qui n'accepte aucun paramtre (nous verrons les requte paramtres galement plus tard). La premire chose faire est de crer un objet Command. Pour cela, on utilise l'interface IDBCreateCommand et sa mthode CreateCommand, partir de la session obtenue lors de l'ouverture de la connexion :
OleDbCheck((FSession as IDBCreateCommand).CreateCommand(nil, IID_ICommandText, unknown));

Puis, on initialise le traitement de la commande avec la requte SQL. Il suffit d'utiliser la mthode SetCommandText de l'interface ICommandText :
cmd := unknown as ICommandText; cmd.SetCommandText(DBGUID_DEFAULT, PWideChar(SQL));

Enfin, il ne reste plus qu' excuter la commande :


OleDbCheck(cmd.Execute(nil, IID_NULL, DbParams, nil, nil));

DbParams est une structure TDBParams permettant de spcifier les paramtres de la requte. Comme cette dernire n'est pas paramtre, on l'initialise de la faon suivante :
// Il n'y a pas de paramtres DbParams.pData := nil; DbParams.cParamSets := 0; DbParams.HACCESSOR := 0;

IID_NULL est un guid spcial indiquant le type d'interface qu'on souhaite obtenir en retour pour lire les rsultats de la requte. Comme la requte ne doit pas renvoyer de donnes, cette valeur permet d'indiquer qu'on n'attend rien en retour. Il faut le dfinir de la faon suivante :
const IID_NULL: TGUID = '{00000000-0000-0000-0000-000000000000}';

Il ne reste plus qu' runir le tout dans une mthode ExecSQL sur notre objet TOleDbConnection :
// Execute une commande SQL qui ne retourne pas de donnes. // C'est la mthode la plus simple pour excuter une requte SQL. procedure TOleDbConnection.ExecSQL(const SQL: widestring; Params : TParams); var t0 : int64; cmd : ICommandText; unknown : IUnknown; OleDbParams : TOleDbParams; begin // Premirement, on trace le dbut de la requte. SQLLogger.TraceSQLBegin(SQL, t0); try try CheckConnected; // On s'assure que la connexion est ouverte. InfoMessages.Clear; // On rinitialise la liste des messages d'information

- 14 Les sources prsentes sur cette page sont libres de droits et vous pouvez les utiliser votre convenance. Par contre, la page de prsentation constitue une uvre intellectuelle protge par les droits d'auteur. Copyright 2009 Franck SORIANO. Aucune reproduction, mme 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 intrts. Cette page est dpose la SACD.

http://fsoriano.developpez.com/articles/db/oledb/delphi/

Utiliser OLEDB en Delphi par Franck SORIANO

// Tout d'abord, il faut crer un objet Command pour excuter la requte. OleDbCheck((FSession as IDBCreateCommand).CreateCommand(nil, IID_ICommandText, unknown)); // Ensuite on initialise la requte SQL. cmd := unknown as ICommandText; cmd.SetCommandText(DBGUID_DEFAULT, PWideChar(SQL)); OleDbParams := TOleDbParams.Create(Params, cmd, self); try // Enfin on excute la commande, sans attendre de rsultats. OleDbCheck(cmd.Execute(nil, IID_NULL, OleDbParams.Parameters, nil, nil)); // On met jour la valeur des paramtres de sorti OleDbParams.UpdateParams(Params); finally OleDbParams.Free; end; except on e:exception do begin // En cas d'erreur, on trace l'exception. SQLLogger.TraceException(e); raise; // Et on redclenche l'erreur. end; end; finally SQLLogger.TraceSQLEnd(SQL, t0); // Pour finir, on logue la fin de la requte end; end;

Bien videmment, on n'oublie pas d'instrumenter la mthode pour tracer l'excution de la requte avec ETW. TraceSQLBegin et TraceSQLEnd vont tracer le dbut et la fin de l'excution. En cas d'erreur, l'exception est ellemme trace avec TraceException avant d'tre redclenche.

- 15 Les sources prsentes sur cette page sont libres de droits et vous pouvez les utiliser votre convenance. Par contre, la page de prsentation constitue une uvre intellectuelle protge par les droits d'auteur. Copyright 2009 Franck SORIANO. Aucune reproduction, mme 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 intrts. Cette page est dpose la SACD.

http://fsoriano.developpez.com/articles/db/oledb/delphi/

Utiliser OLEDB en Delphi par Franck SORIANO

II-D - Excuter une requte de type SELECT


Maintenant, on va pouvoir commencer les choses srieuses : Faire une requte de type SELECT. Ce type de requte est identique aux requtes prcdentes. Cependant, lorsque le serveur rend la main aprs avoir excut la commande, il reste encore lire les donnes. Nous allons dfinir la fonction OpenSQL dans la classe TOleDbConnection. Cette dernire attendra une requte SQL en entre et renverra en sorti un objet TDataSet, initialis et charg avec le rsultat de la requte. Le code est trs similaire la mthode ExecSQL :
// Execute une requte SQL qui renvoie des donnes. // La fonction retourne un DataSet dconnect, initialis avec le jeu de donnes obtenu function TOleDbConnection.OpenSQL(const SQL: widestring; Params: TParams; FetchSize : cardinal): TDataSet; var t0 : int64; ds : TOleDbDataSet; RowSet : IRowSet; unknown : IUnknown; cmd : ICommandText; OleDbParams : TOleDbParams; begin ds := nil; // Premirement, on trace le dbut de la requte. SQLLogger.TraceSQLBegin(SQL, t0); try try CheckConnected; // On s'assure que la connexion est ouverte. InfoMessages.Clear; // Il faut crer un objet Command pour excuter la requte OleDbCheck((FSession as IDBCreateCommand).CreateCommand(nil, IID_ICommandText, unknown)); // On initialise la requte SQL. cmd := unknown as ICommandText; cmd.SetCommandText(DBGUID_DEFAULT, PWideChar(SQL)); unknown := nil; // Si la requte est paramtre, il faut initialiser les valeurs des paramtres. C'est le rle // de la classe TOleDbParams. Si elle n'attend pas de paramtre, TOleDbParams dfinit un jeu // de paramtres vide. OleDbParams := TOleDbParams.Create(Params, Cmd, self); try OleDbCheck(cmd.Execute(nil, IID_IRowset, OleDbParams.Parameters, nil, @unknown)); if Assigned(unknown) then begin RowSet := unknown as IRowSet; unknown := nil; // La source de donnes vient de rendre la main. On peut lire les donnes. ds := TOleDbDataSet(CreateDataSet); // On instancie le DataSet qui sera retourn. ds.LoadFromRowSet(RowSet, self, FetchSize); // Lecture des donnes. RowSet := nil; end else ds := nil; // Aprs l'excution de la requte on met jour les valeurs des paramtres de sorti. OleDbParams.UpdateParams(Params); finally OleDbParams.Free; end; except on e:exception do

- 16 Les sources prsentes sur cette page sont libres de droits et vous pouvez les utiliser votre convenance. Par contre, la page de prsentation constitue une uvre intellectuelle protge par les droits d'auteur. Copyright 2009 Franck SORIANO. Aucune reproduction, mme 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 intrts. Cette page est dpose la SACD.

http://fsoriano.developpez.com/articles/db/oledb/delphi/

Utiliser OLEDB en Delphi par Franck SORIANO

begin SQLLogger.TraceException(e); if Assigned(ds) then ds.Free; raise; end; end; finally // Pour finir, on trace la fin de la requte if Assigned(ds) then SQLLogger.TraceSQLEnd(SQL + ', ' + IntToStr(ds.RecordCount) + ' Lignes', t0) else SQLLogger.TraceSQLEnd(SQL, t0, Params); end; result := ds; end;

En fait, le code est identique, jusqu'au moment d'excuter la commande. L'excution de la commande s'effectue un peu diffremment :
// Excution de la requte sur la source de donnes. OleDbCheck(cmd.Execute(nil, IID_IRowset, DbParams, nil, @unknown)); RowSet := unknown as IRowSet;

Cette fois, au lieu d'utiliser IID_NULL, on demande une interface IRowSet avec IID_Rowset. C'est cette dernire qui va permettre de lire le rsultat. Ensuite, il faut crer le dataset qui sera renvoy. Nous allons retourner un objet TMemoryDataSet. Il s'agit du dataset en mmoire que nous avons dfinit dans l'article prcdent. Le chargement OLEDB va tirer parti de l'organisation interne des donnes dans TMemoryDataSet pour s'effectuer la vitesse grand V. Nous allons driver TMemoryDataSet en TCustomOleDbDataSet et TOleDbDataSet et lui dlguer la gestion de ce chargement :
// La source de donnes vient de rendre la main. On peut lire les donnes. ds := TOleDbDataSet(CreateDataSet); // On instancie le DataSet qui sera retourn. ds.LoadFromRowSet(unknown as IRowSet, self); // Lecture des donnes.

- 17 Les sources prsentes sur cette page sont libres de droits et vous pouvez les utiliser votre convenance. Par contre, la page de prsentation constitue une uvre intellectuelle protge par les droits d'auteur. Copyright 2009 Franck SORIANO. Aucune reproduction, mme 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 intrts. Cette page est dpose la SACD.

http://fsoriano.developpez.com/articles/db/oledb/delphi/

Utiliser OLEDB en Delphi par Franck SORIANO

Voyons comment s'effectue la lecture des donnes dans LoadFromRowSet :


// Charge les donnes retournes par RowSet dans le Dataset. // Le dataset est compltement initialis : Les champs sont crs, le dataset // est ouvert et charg avec les donnes. procedure TOleDbDataSet.LoadFromRowSet(RowSet: IRowSet; Cnt : TOleDbConnection; FetchSize : cardinal); var t0 : int64; begin SQLLogger.TraceBegin(EVENT_SQL_START, ' Debut Fetch', t0); try Close; // On s'assure que le Dataset est ferm. FPageSize := FetchSize; Describe(RowSet as IColumnsInfo, cnt); // Il faut dfinir les champs en fonction du rowset. if FetchSize<>0 // On initialise le FetchSize qui nous a t fournit then FPageSize := FetchSize else begin // Sinon, on essaie de calculer une valeur adapte. FPageSize := 8192 div RecordSize; if FPageSize < 1 then FPageSize := 1; end; SQLLogger.Trace(EVENT_SQL_INFO, Format(' TRACE_LEVEL_VERBOSE); RecordSize=%d, PageSize=%d', [RecordSize, FPageSize]),

Open; // On doit ouvrir le dataset pour pouvoir charger les donnes. FetchAll(RowSet, cnt); // Charge les donnes du rowset dans le dataset. First; // On se positionne au dbut du DataSet. finally SQLLogger.TraceEnd(EVENT_SQL_END, ' Fin Fetch', t0); end; end;

Tout d'abord, la mthode est instrumente pour indiquer le dbut et la fin du traitement. De cette faon, on pourra obtenir des traces d'excution indiquant prcisment le temps pass l'excution de la requte et le temps pass lire les donnes. Ensuite, on peut voir que le chargement est fait en quatre tapes : Premirement, on dfinit les champs du DataSet en fonction du rsultat de la requte, c'est--dire en fonction de la structure des donnes dans RowSet. On effectue cette opration avec la mthode Describe dveloppe spcialement pour ce rle. Ensuite, on ouvre le DataSet. L'ouverture engendre la cration des TField et l'initialisation des buffers internes. A ce moment, le dataset est encore vide. Il ne reste alors plus qu' l'alimenter avec les donnes. Cette opration est faite avec la mthode FetchAll. Enfin, on fait un First pour repositionner le Dataset sur le premier enregistrement et recharger les buffers de la ligne en cours.

II-D-1 - Lecture de la structure du jeu de donne : Describe


OLEDB nous retourne une interface IRowSet pour lire les donnes. Mais pour pouvoir faire la lecture encore fautil savoir quels sont les champs lire ! Pour cela, on dispose de la mthode GetColumnInfo de l'interface IColumnsInfo. Cette dernire retourne un tableau de structures DBCOLUMNINFO dcrivant chaque champ l'intrieur du jeu de rsultat.
var nbColumns : cardinal; ColumnsInfo : PDBColumnInfo; infos : PWideChar; ... // On appelle GetColumnInfo pour obtenir un tableau dcrivant la liste des colonnes prsentes. OleDbCheck((RowSet as IColumnsInfo).GetColumnInfo(nbColumns, ColumnsInfo, Infos));
- 18 Les sources prsentes sur cette page sont libres de droits et vous pouvez les utiliser votre convenance. Par contre, la page de prsentation constitue une uvre intellectuelle protge par les droits d'auteur. Copyright 2009 Franck SORIANO. Aucune reproduction, mme 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 intrts. Cette page est dpose la SACD.

http://fsoriano.developpez.com/articles/db/oledb/delphi/

Utiliser OLEDB en Delphi par Franck SORIANO

En sortie, nbColumns indique le nombre de champs prsents dans RowSet, ColumnsInfo pointe sur un tableau de structures DBCOLUMNINFO et infos est un buffer contenant les noms des champs. Il ne reste plus qu' parcourir le tableau ColumnsInfo pour crer les TFieldDef du dataset :
procedure TCustomOleDbDataSet.Describe(RowSet: IColumnsInfo; Cnt : TCustomOleDbConnection); var nbColumns : cardinal; ColumnsInfo : PDBColumnInfo; infos : PWideChar; Column : PDBColumnInfo; columnName : widestring; DataType : TFieldType; DataSize : cardinal; i : integer; n : integer; IsNullable : boolean; begin // On appelle GetColumnInfo pour obtenir un tableau dcrivant la liste des colonnes prsentes. Cnt.OleDbCheck(RowSet.GetColumnInfo(nbColumns, ColumnsInfo, Infos)); try FieldDefs.BeginUpdate; Column := ColumnsInfo; // Pointe sur la description de la colonne en cours try FieldDefs.Clear; for i := 0 to nbColumns-1 do begin DataSize := 0; // Si la colonne n'a pas t nomme, on lui attribue un nom automatique // sous la forme colX avec X indiquant le numro de la colonne ( partir 1) if Assigned(Column.pwszName) then columnName := Column.pwszName else columnName := 'col' + IntToStr(i+1); if Trim(columnName) = '' then columnName := 'col' + IntToStr(i+1); // Ensuite il faut vrifier si le nom n'existe pas dj et renommer les // colonnes automatiquement en cas de besoin. if FieldDefs.IndexOf(columnName)<>-1 then begin n := 1; columnName := columnName + '_'; while FieldDefs.IndexOf(columnName + IntToStr(n))<>-1 do begin inc(n); end; columnName := columnName + IntToStr(n); end; // A prsent, on peut commencer crer les champs. case Column.wType of DBTYPE_I1, DBTYPE_UI1, DBTYPE_I2: // Entier court sign DataType := ftSmallint; DBTYPE_UI2: // Entier court non sign DataType := ftWord; DBTYPE_UI4, DBTYPE_I4: // Entier, sign ou non sign DataType := ftInteger; DBTYPE_CY: // Type currency. DataType := ftBCD; DBTYPE_DATE, DBTYPE_DBDATE, DBTYPE_DBTIME: // Dates DataType := ftDateTime; DBTYPE_DBTIMESTAMP: // DateTime prcis. DataType := ftTimeStamp; DBTYPE_BOOL: // Boolen DataType := ftBoolean; DBTYPE_R4, DBTYPE_R8, DBTYPE_DECIMAL, DBTYPE_NUMERIC: // Nombre dcimal DataType := ftFloat; DBTYPE_I8, DBTYPE_UI8: // Entier long
- 19 Les sources prsentes sur cette page sont libres de droits et vous pouvez les utiliser votre convenance. Par contre, la page de prsentation constitue une uvre intellectuelle protge par les droits d'auteur. Copyright 2009 Franck SORIANO. Aucune reproduction, mme 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 intrts. Cette page est dpose la SACD.

http://fsoriano.developpez.com/articles/db/oledb/delphi/

Utiliser OLEDB en Delphi par Franck SORIANO

DataType := ftLargeint; DBTYPE_GUID: // GUID begin DataType := ftGuid; DataSize := 38; end; DBTYPE_BYTES, DBTYPE_UDT: // Binaire DataType := ftBlob; DBTYPE_STR: // Chane de caractres begin // Si la longueur du champ dpasse une certaine longueur, on en fait un memo. if Column.ulColumnSize<COLUMN_MAXSIZE then begin DataType := ftString; DataSize := Column.ulColumnSize; end else DataType := ftMemo; end; DBTYPE_WSTR: // Chane de caractres unicode UTF-16. begin // Si la longueur du champ dpasse une certaine longueur, on en fait un memo unicode. if Column.ulColumnSize <COLUMN_MAXSIZE then begin DataType := ftWideString; DataSize := Column.ulColumnSize; end else DataType := ftWideMemo; end; else begin raise EOleDbException.Create( Format('Le type (%d) de la colonne %s n''est pas support !', [Column.wType, columnName])); end; end; IsNullable := (ColumnsInfo.dwFlags and DBCOLUMNFLAGS_MAYBENULL) <>0; try TFieldDef.Create(FieldDefs, columnName, DataType, DataSize, not IsNullable, FieldDefs.Count); except on e : EDatabaseError do begin raise EOleDbException.CreateFmt('Erreur lors de la cration du champ %s : %s', [columnName, e.Message]); end; end; // On peut passer la colonne suivante : integer(Column) := integer(Column) + sizeof(DBCOLUMNINFO); end; finally FieldDefs.EndUpdate; end; CalcRecordSize; // calcule le buffer de stockage des lignes finally Cnt.FMAlloc.Free(ColumnsInfo); Cnt.FMAlloc.Free(Infos); end; end;

Le code ci-dessus tient compte de quelques particularits : Pour crer les TFieldDef, tous les champs doivent avoir un nom et on ne doit pas avoir deux champs avec le mme nom. Or si la requte d'origine retourne des champs calculs sans les avoir nomms, SQL Server va nous renvoyer un jeu de rsultats avec des champs sans noms. De mme si on fait une jointure sur deux

- 20 Les sources prsentes sur cette page sont libres de droits et vous pouvez les utiliser votre convenance. Par contre, la page de prsentation constitue une uvre intellectuelle protge par les droits d'auteur. Copyright 2009 Franck SORIANO. Aucune reproduction, mme 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 intrts. Cette page est dpose la SACD.

http://fsoriano.developpez.com/articles/db/oledb/delphi/

Utiliser OLEDB en Delphi par Franck SORIANO

tables et qu'on retourne des champs qui portent le mme nom, on obtiendra des champs de mme nom dans le jeu de rsultats. Aussi, Describe nomme automatiquement les champs sans nom et les renomme en cas de doublons. Avec SQL Server, les champs dont la longueur n'est pas limite (varchar(max), varbinary(max)) remontent comme tant des champs de longueur -1 (ou $FFFFFFFF si on est en non sign). Describe teste ce cas particulier pour en faire des ftMemo. Lorsqu'on a fini de dfinir les TFieldDef, la classe TMemoryDataSet a besoin qu'on appelle CalcRecordSize avant qu'on puisse intervenir directement sur les buffers internes. Cette mthode est appele automatiquement l'ouverture du dataset. Cependant par scurit, il vaut mieux l'appeler ds qu'on a finit de modifier FieldDefs. Enfin, lorsqu'on a finit de travailler avec ColumnsInfo, il faut librer la mmoire qui a t alloue par OLEDB. Cette dernire a t alloue avec le gestionnaire de mmoire COM. Il faut la librer de la mme faon avec IMAlloc.Free.

Remarque sur les types de donnes utiliss


Describe doit effectuer un mapping entre les types de donnes physiques retourns par OLEDB et les types de donnes Delphi. Ces derniers ne se correspondent pas strictements. Ce n'est pas trs grave, car OLEDB pourra effectuer certaines conversions automatiquement au moment du chargement des donnes. On remarquera cependant que : Les types dates donnent des champs ftDateTime ou ftTimeStamp. Il n'est pas vraiment ncessaire de les dtailler en ftDate, ftDateTime et ftTime puisque SQL Server ne gre qu'un seul type de donnes. Les nombres dcimaux deviennent des ftFloat. Ca signifie que les champs de type decimal qui sont stocks en virgule fixe dans la base de donnes seront convertis en flottant dans Delphi. C'est un peu gnant en soit puisqu'on est alors contraint travailler avec des flottants alors qu'on voulait des valeurs exactes. Cependant avec l'architecture db de Delphi on n'a pas vraiment d'autres choix. En effet, le type decimal peut tre configur avec un nombre quelconque de chiffres aprs la virgule. Or la seule faon de grer a en virgule fixe serait de passer par le type FMTBcd, puis de n'utiliser que ce type dans tous les calculs. Seulement OLEDB ne gre pas les FMTBcd, il utilise le type DBTYPE_NUMERIC pour grer les virgules fixes. Par contre, le type money (DBTYPE_CY) est un type dcimale, en virgule fixe avec 4 dcimales. Il correspond exactement au type currency de Delphi. Ce dernier est gr dans Describe en le mappant sur ftBCD. En effet, contrairement ce qu'on pourrait penser, le ftBCD est en ralit stock dans les datasets sous la forme d'un currency (ftCurrency est en ralit un double...) Pour les GUID, on utilise le type ftGUID. On remarque cependant que pour un GUID, la taille du champ est de 38, alors que normalement un GUID ne fait que 16 octets. C'est parce que en ralit, Delphi ne gre pas les GUID comme type de champ db. La classe TGUIDField stocke le guid sous la forme d'un string. On dfinit donc un champ ftGUID de longueur 38, mais au moment de charger rellement les donnes, on dira OLEDB qu'il s'agit d'un champ de type string

II-D-2 - Chargement des donnes : FetchAll


Avec OLEDB, le chargement des donnes dans le dataset va s'effectuer la vitesse grand V. En effet au lieu de devoir lire les donnes ligne par ligne et champ par champ, on va simplement demander OLEDB de remplir directement les buffers utiliss par TMemoryDataSet.

II-D-2-a - Dfinition du Binding


Pour raliser cette opration, on doit dfinir un binding entre la structure logique du jeu de donnes et la structure interne du buffer dans lequel on va le charger. Ce binding est dfini par l'intermdiaire d'un Accesseur dans OLEDB. On cre un accesseur avec la mthode CreateAccessor de l'interface IAccessor. Les accesseurs peuvent tre de plusieurs types. Il y a notamment les accesseurs de donnes (DBACCESSOR_ROWDATA) qui dfinissent le binding a utiliser pour lire ou crire les donnes d'une ligne du jeu de donnes et les accesseurs pour les
- 21 Les sources prsentes sur cette page sont libres de droits et vous pouvez les utiliser votre convenance. Par contre, la page de prsentation constitue une uvre intellectuelle protge par les droits d'auteur. Copyright 2009 Franck SORIANO. Aucune reproduction, mme 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 intrts. Cette page est dpose la SACD.

http://fsoriano.developpez.com/articles/db/oledb/delphi/

Utiliser OLEDB en Delphi par Franck SORIANO

paramtres (DBACCESSOR_PARAMETERDATA). Ces derniers servent lire/crire les valeurs des paramtres pour les requtes paramtres. Le chargement des donnes doit ainsi dbuter par la cration d'un accesseur :
OleDbCheck((RowSet as IAccessor).CreateAccessor( DBACCESSOR_ROWDATA + DBACCESSOR_OPTIMIZED, FieldDefs.Count, @Bindings[0], RecordSize, Accessor, nil));

Bindings est un tableau de structures DBBINDING dfinissant le binding utiliser pour chaque champ. Il faut le renseigner avant de crer l'accesseur. Pour cela, on dfinit la mthode InitializeBindings. Cette dernire va initialiser le tableau en fonction des champs dfinis dans les FieldDefs, et donc de l'organisation des buffers de stockage l'intrieur de la classe TMemoryDataset :
procedure TCustomOleDbDataSet.InitializeBindings; var i : integer; FieldDef : TFieldDef; Binding : PDBBinding; begin SetLength(Bindings, FieldDefs.Count); for i := 0 to FieldDefs.Count-1 do begin FieldDef := FieldDefs[i]; Binding := @Bindings[i]; Binding.iOrdinal := i+1; Binding.pTypeInfo := nil; Binding.obValue := FFieldInfo[i].Offset+8; // Offset de la parti donnes du champ Binding.obLength := FFieldInfo[i].Offset+4; // Offset de la parti longueur du champ Binding.obStatus := FFieldInfo[i].Offset; // offset de la parti status du champ Binding.cbMaxLen := FFieldInfo[i].Size; // taille de la zone donnes en octets. Binding.dwPart := DBPART_VALUE or DBPART_LENGTH or DBPART_STATUS; // Les trois champs sont dfinis Binding.pObject := nil; Binding.pBindExt := nil; Binding.dwFlags := 0; Binding.eParamIO := DBPARAMIO_NOTPARAM; // Il ne s'agit pas d'un paramtre Binding.dwMemOwner:= DBMEMOWNER_CLIENTOWNED; // Le client est propritaire de la mmoire. Binding.bPrecision:= 0; Binding.bScale := 0; Binding.wType := DBTYPE_STR; case FieldDef.DataType of ftString, ftFixedChar: Binding.wType := DBTYPE_STR; ftSmallint : Binding.wType := DBTYPE_I2; ftWord: Binding.wType := DBTYPE_UI2; ftAutoInc, ftInteger: Binding.wType := DBTYPE_I4; ftBoolean: Binding.wType := DBTYPE_BOOL; ftFloat: Binding.wType := DBTYPE_R8; ftCurrency: Binding.wType := DBTYPE_R8; ftBCD: Binding.wType := DBTYPE_CY; ftDate, ftTime, ftDateTime : Binding.wType := DBTYPE_DATE; ftTimeStamp: // Chane de caractres // Entier court sign // Entier court non sign // Entier sign // Boolen // Nombre flottant sur 64 bits // Nombre flottant sur 64 bits // Nombre dcimal en virgule fixe (currency) // Date de type TDateTime // Date de type TTimeStamp

- 22 Les sources prsentes sur cette page sont libres de droits et vous pouvez les utiliser votre convenance. Par contre, la page de prsentation constitue une uvre intellectuelle protge par les droits d'auteur. Copyright 2009 Franck SORIANO. Aucune reproduction, mme 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 intrts. Cette page est dpose la SACD.

http://fsoriano.developpez.com/articles/db/oledb/delphi/

Utiliser OLEDB en Delphi par Franck SORIANO

Binding.wType := DBTYPE_DBTIMESTAMP; ftLargeint: // entier long 64 bits Binding.wType := DBTYPE_I8; ftWideString, ftFixedWideChar: // Chane de caractres UNICODE Binding.wType := DBTYPE_WSTR; ftMemo: // Le champ est un pointeur sur une chaine de caractres. Binding.wType := DBTYPE_STR + DBTYPE_BYREF; ftBlob: // Le champ est un pointeur sur un tableau binaire Binding.wType := DBTYPE_BYTES + DBTYPE_BYREF; ftWideMemo: // Le champ est un pointeur sur une chaine de caractres unicode. Binding.wType := DBTYPE_WSTR + DBTYPE_BYREF; ftGUID: // Les GUID sont stocks en chane de caractres Binding.wType := DBTYPE_STR; else raise EOleDbException.Createfmt('Le type du champ %s n''est pas support !', [FieldDef.Name]); end; end; end;

- 23 Les sources prsentes sur cette page sont libres de droits et vous pouvez les utiliser votre convenance. Par contre, la page de prsentation constitue une uvre intellectuelle protge par les droits d'auteur. Copyright 2009 Franck SORIANO. Aucune reproduction, mme 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 intrts. Cette page est dpose la SACD.

http://fsoriano.developpez.com/articles/db/oledb/delphi/

Utiliser OLEDB en Delphi par Franck SORIANO

Les structures DBBINDING sont renseignes de la faon suivante : Champ iOrdinal obValue obLength obStatus cbMaxLen dwPart eParamIO Valeur Indique le numro de la colonne ( partir de 1). On le dfinit simplement avec l'index du champ. Cette zone indique o placer la valeur du champ l'intrieur du buffer de destination. On doit indiquer l'offset de la zone de donne par rapport au dbut du buffer. Cette zone indique o mmoriser la longueur effective du champ pour les champs de longueur variable (varchar, nvarchar...). Cette zone indique o mmoriser le status du champ. Il s'agit principalement d'indiquer si le champ possde une valeur ou s'il vaut NULL dans la base de donnes. Cette zone indique la longueur maximale disponible l'intrieur du buffer pour stocker les donnes du champ. Si la taille relle du champ dpasse cette valeur, les donnes seront tronques. Il s'agit d'un masque qu'on doit renseigner pour indiquer si les attributs obValue, obLength et obStatus ont t dfinis dans la structure et donc si leur valeur est valide. Ce champ sert pour les accesseurs dfinissant des paramtres. Il sert indiquer s'il s'agit d'un paramtre d'entre ou de sorti. Pour un accesseur de donnes, on dfinit la valeur DBPARAMIO_NOTPARAM Ce champ est important lorsqu'on veut lire les donnes d'un champ dfini par rfrence (Cf wType). Il permet d'indiquer qui est propritaire de la zone mmoire pointe par la rfrence. Avec DBMEMOWNER_CLIENTOWNED, c'est le client qui possde la mmoire et qui sera responsable de sa libration. Pour les champs qui ne sont pas dfinis par rfrence, dwMemOwner doit obligatoirement tre dfinit DBMEMOWNER_CLIENTOWNED. Ce champ est trs important, c'est lui qui dfinit le type de donnes du champ. Il dfinit ainsi le format de stockage dans le buffer. Si on dfinit un type diffrent du type rel du champ, OLEDB se chargera d'effectuer les conversions avant d'alimenter le buffer. Ainsi pour les dates, on peut demander le type DBTYPE_DATE et laisser le provider se dbrouiller pour nous fournir un TDateTime. Il est possible de spcifier l'option DBTYPE_BYREF dans le type de donnes. Ca signifie que le champ ne contient pas directement la valeur, mais un pointeur sur cette valeur.

dwMemOwner

wType

Ainsi, on se trouve dans la configuration suivante :

- 24 Les sources prsentes sur cette page sont libres de droits et vous pouvez les utiliser votre convenance. Par contre, la page de prsentation constitue une uvre intellectuelle protge par les droits d'auteur. Copyright 2009 Franck SORIANO. Aucune reproduction, mme 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 intrts. Cette page est dpose la SACD.

http://fsoriano.developpez.com/articles/db/oledb/delphi/

Utiliser OLEDB en Delphi par Franck SORIANO

En fait, les structures DBBINDING sont quivalentes la classe TFieldDef. Elles dfinissent l'organisation des donnes l'intrieur des buffers des lignes du dataset final. Pour les champs LOB (les champs dont la longueur maximale n'est pas limite comme les memos, binaires...), on est confront au fait qu'on ne peut pas connatre priori la longueur du champ. On ne peut donc pas dfinir un buffer de taille suffisante pour charger la totalit du LOB. Avec OLEDB, on peut rsoudre le problme trs facilement grce aux champs dfinis par rfrence. Lors de la dfinition du type du binding, on positionne le flag DBTYPE_BYREF. Avec ce dernier, OLEDB ne remplira pas directement notre buffer avec les donnes du champ, mais dfinira un pointeur sur un autre buffer qui lui contiendra rellement les donnes voulues. Le buffer est allou et rempli par le provider OLEDB. Il faudra simplement qu'on recopie cette valeur dans le dataset.

II-D-2-b - Lecture des donnes


Une fois l'accesseur dfini, on peut s'occuper rellement de la lecture des donnes. Elle s'effectue en deux temps : Dans un premier temps, on doit appeler la mthode GetNextRows pour demander au provider de lire un certain nombre de lignes auprs du SGBD. Durant cette tape, les lignes sont lues par blocs, mais les donnes restent l'intrieur du provider. Elles seront renvoyes au client ligne par ligne lorsque ce dernier les demandes. Ainsi, le provider commence par lire un certain nombre de lignes avant de les renvoyer au client. Ce nombre de lignes est ce qu'on appelle gnralement le "prefetch". On effectue ce premier chargement de la faon suivante :
// Dans un premier temps, il faut demander au provider de rcuprer les lignes depuis le serveur. // Elles restent stockes dans des buffers internes au provider. FetchResult := RowSet.GetNextRows(DB_NULL_HCHAPTER, 0, // On lit les lignes depuis la dernire lecture FPageSize, // On lit autant de ligne que dans une page du dataset. nbRow, // Nombre de lignes rellement lues. RowHandle); Cnt.OleDbCheck(FetchResult);

GetNextRows retourne dans RowHandle un tableau contenant un handle pour chaque ligne qui a t lue. On peut ensuite utiliser ces handles pour obtenir les donnes des lignes. On remarquera au passage qu'aprs GetNextRows, on peut accder directement n'importe quelle ligne partir de son handle. Il n'est pas ncessaire de respecter l'ordre squentiel.

- 25 Les sources prsentes sur cette page sont libres de droits et vous pouvez les utiliser votre convenance. Par contre, la page de prsentation constitue une uvre intellectuelle protge par les droits d'auteur. Copyright 2009 Franck SORIANO. Aucune reproduction, mme 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 intrts. Cette page est dpose la SACD.

http://fsoriano.developpez.com/articles/db/oledb/delphi/

Utiliser OLEDB en Delphi par Franck SORIANO

Lorsqu'on dispose du handle d'une ligne, on peut lire ses donnes grce l'accesseur dfini prcdemment :
Cnt.OleDbCheck(RowSet.GetData(RowHandle[i], Accessor, Data));

Data est un pointeur sur le buffer destin recevoir les donnes de la ligne. C'est--dire le buffer de stockage d'une ligne du dataset. Accessor dsigne l'accesseur qui dfinit l'organisation du buffer Data.

On peut appeler GetData autant de fois qu'on veut pour une mme ligne. Dans l'implmentation de TOleDbDataset, on a choisit de dfinir un seul accesseur qui regroupe tous les champs de la ligne. Cependant on aurait aussi pu dfinir un accesseur par champ et charger les champs un par un en appelant GetData autant de fois que ce qu'on a dfinit d'accesseurs (c'est ce que semble faire ADO). Lorsqu'on a fini de travailler avec les donnes d'une ligne, on doit demander au provider de dtruire les handles des lignes qu'il a crs. C'est ce qu'on fait avec ReleaseRows :
RowSet.ReleaseRows(nbRow, RowHandle, nil, nil, nil);

Cette tape permet galement au provider de librer la mmoire alloue pour stocker les lignes. Il ne reste plus qu' runir le tout pour crire la mthode FetchAll :
// Charge les donnes retourne par IRowSet l'intrieur du dataset ds. procedure TOleDbDataSet.FetchAll(RowSet: IRowSet; Cnt : TOleDbConnection); var Accessor : HACCESSOR; nbRow : cardinal; RowHandle : PUintArray; FetchResult : hResult; i,j : integer; FPosition : cardinal; Data : Pointer; FIeldData : PFieldData; FieldOffset : cardinal; ptrBlob : pointer; begin // Premirement, il faut configurer le binding par rapport la structure des lignes du dataset. InitializeBindings; // Ensuite, on cre un accesseur bas sur ce binding. Cnt.OleDbCheck((RowSet as IAccessor).CreateAccessor( DBACCESSOR_ROWDATA + DBACCESSOR_OPTIMIZED, FieldDefs.Count, @Bindings[0], RecordSize, Accessor, nil)); try // A prsent, il ne reste plus qu' lire les lignes. nbRow := 0; // Les lignes vont tre lues par blocs de FPageSize lignes. // Il faut commencer par allouer un tableau qui contiendra les handles de ces lignes. GetMem(RowHandle, FPageSize*sizeof(integer)); try repeat // Dans un premier temps, il faut demander au provider de rcuprer les lignes depuis le serveur. // Elles restent stockes dans des buffers internes au provider. FetchResult := RowSet.GetNextRows(DB_NULL_HCHAPTER, 0, // On lit les lignes depuis la dernire lecture FPageSize, // On lit autant de ligne que dans une page du dataset. nbRow, // Nombre de lignes rellement lues. RowHandle);
- 26 Les sources prsentes sur cette page sont libres de droits et vous pouvez les utiliser votre convenance. Par contre, la page de prsentation constitue une uvre intellectuelle protge par les droits d'auteur. Copyright 2009 Franck SORIANO. Aucune reproduction, mme 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 intrts. Cette page est dpose la SACD.

http://fsoriano.developpez.com/articles/db/oledb/delphi/

Utiliser OLEDB en Delphi par Franck SORIANO

Cnt.OleDbCheck(FetchResult); // Les lignes ont t lues on peut demander au provider d'alimenter notre dataset avec les // rsultats de cette lecture. for i := 0 to nbRow-1 do begin // On calcul le pointeur sur la ligne, dans les pages de stockage de TMemoryDataSet. Data := GetNewline(FPosition); // Ensuite on demande OLEDB de remplir la ligne pointe avec les donnes, en respectant // le binding dfinit dans l'accesseur. Cnt.OleDbCheck(RowSet.GetData(RowHandle[i], Accessor, Data)); // Une fois les donnes retournes par le provider, il reste convertir // les LOB pour les stocker dans des TMemoryBlobStream. for j := 0 to FBlobFields.Count -1 do begin // On commence par obtenir le pointeur sur les donnes du champ FieldOffset := FFieldInfo[cardinal(FBlobFields[j])-1].Offset; FieldData := GetPFieldData(Data, FieldOffset); ptrBlob := pointer(FieldData^.Data); // On recopie les donnes du Blob l'intrieur du DataSet. LoadBlobField(Data, cardinal(FBlobFields[j]), ptrBlob); // Enfin il ne faut pas oublier de librer la mmoire alloue par le provider // OLEDB pour stocker le BLOB. Cette mmoire n'a t alloue que si le champ // ne vaut pas NULL. if FieldData.NullStatus = DBSTATUS_S_OK then cnt.FMAlloc.Free(ptrBlob); end; // Enfin, on dfinit le bookmark de la ligne. DefineBookmark(FPosition, Data); end; // La page vient d'tre traite, on peut dire au provider de librer les lignes. RowSet.ReleaseRows(nbRow, RowHandle, nil, nil, nil); until FetchResult = DB_S_ENDOFROWSET; finally Freemem(RowHandle); end; finally // Il reste librer l'accesseur. Cnt.OleDbCheck((RowSet as IAccessor).ReleaseAccessor(Accessor, nil)); end; end;

Comme on peut le constater, les donnes des lignes sont directement charges l'intrieur des buffers de la classe TMemoryDataSet. On appelle GetNewLine pour ajouter une ligne vide dans TMemoryDataSet et obtenir le pointeur sur le buffer correspondant. Ensuite les donnes de la ligne sont charges avec GetData. Cependant, on doit quand mme effectuer une conversion sur les champs LOB. En effet ces derniers ne sont pas stocks correctement puisque le champ du LOB l'intrieur du buffer de la ligne contient un pointeur sur les donnes du LOB au lieu d'un numro de LOB. Cette conversion est faite en appelant LoadBlobField. Une fois le LOB converti, on peut librer la mmoire qui a t alloue par OLEDB en appelant IMAlloc.Free. Il ne reste plus qu' dfinir son bookmark avec DefineBookmark.

- 27 Les sources prsentes sur cette page sont libres de droits et vous pouvez les utiliser votre convenance. Par contre, la page de prsentation constitue une uvre intellectuelle protge par les droits d'auteur. Copyright 2009 Franck SORIANO. Aucune reproduction, mme 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 intrts. Cette page est dpose la SACD.

http://fsoriano.developpez.com/articles/db/oledb/delphi/

Utiliser OLEDB en Delphi par Franck SORIANO

II-E - Requtes paramtres


Une requte paramtre est une requte ordinaire, sauf qu'au moment d'excuter la requte, on doit galement fournir la valeur des paramtres. Pour cela, on passe une structure de type DBPARAMS en paramtre de la mthode execute. Cette dernire se compose d'un buffer qui contient les valeurs des paramtres, ainsi que d'un accesseur dcrivant comment interprter le buffer. Dans l'architecture db de Delphi, les paramtres sont dfinis sous la forme d'une collection TParams d'objets TParam. Si on veut garder ce principe, on doit recopier les valeurs de ces paramtres dans un buffer, et crer l'accesseur correspondant. C'est le rle de la classe TOleDbParams. L'initialisation du buffer et de son accesseur est ralise par son constructeur :
constructor TOleDbParams.Create(Params: TParams; ACmd : ICommand; Cnt : TOleDbConnection); var i : integer; Param : TParam; PData : PFieldData; Offset : cardinal; ParamSize : cardinal; Binding : PDBBINDING; value : cardinal; DateValue : TDatetime; FloatValue : double; StringValue : string; WidestringValue : widestring; begin FCnt := Cnt; if Assigned(Params) and (Params.Count >0) then begin SetLength(Bindings, Params.Count); SetLength(FParamInfo, Params.Count); FCapacity := 4096; SetLength(FData, 4096); PData := @FData[0]; Offset := 0; for i := 0 to Params.Count -1 do begin Param := Params[i]; Binding := @Bindings[i]; // Initialisation de l'index des paramtres. FParamInfo[i].Offset := Offset; FParamInfo[i].Size := Param.Size; ParamSize := 0; // On prinitialise le binding pour le paramtre courant. Binding.iOrdinal := i+1; Binding.pTypeInfo := nil; Binding.obValue := Offset + 8; Binding.obLength := Offset + 4; Binding.obStatus := Offset; Binding.cbMaxLen := Param.Size; Binding.dwPart := DBPART_VALUE or DBPART_LENGTH or DBPART_STATUS; Binding.pObject := nil; Binding.pBindExt := nil; Binding.dwFlags := 0; Binding.dwMemOwner:= DBMEMOWNER_CLIENTOWNED;
- 28 Les sources prsentes sur cette page sont libres de droits et vous pouvez les utiliser votre convenance. Par contre, la page de prsentation constitue une uvre intellectuelle protge par les droits d'auteur. Copyright 2009 Franck SORIANO. Aucune reproduction, mme 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 intrts. Cette page est dpose la SACD.

http://fsoriano.developpez.com/articles/db/oledb/delphi/

Utiliser OLEDB en Delphi par Franck SORIANO

Binding.bPrecision:= 0; Binding.bScale := 0; Binding.wType := DBTYPE_STR; // wType sera rdfinit ensuite en fonction du paramtre. // On dfinit eParamIO en fonction du type dfinit pour le paramtre case Param.ParamType of ptUnknown, ptInput: Binding.eParamIO := DBPARAMIO_INPUT; ptOutput : Binding.eParamIO := DBPARAMIO_OUTPUT; ptInputOutput : Binding.eParamIO := DBPARAMIO_INPUT or DBPARAMIO_OUTPUT; end; // Premirement on regarde si le paramtre est NULL. Si c'est le cas, il // faut indiquer DBSTATUS_S_ISNULL pour le status du champ. if Param.IsNull then PData^.NullStatus := DBSTATUS_S_ISNULL else PData^.NullStatus := DBSTATUS_S_OK; // Maintenant, on fait la copie de la valeur du paramtre dans FData // fonction du type de ce dernier. case Param.DataType of ftWord, ftBoolean: begin ParamSize := 2; CheckSize(Offset + 8 + ParamSize); value := Param.AsInteger; move(value, PData^.Data[0], ParamSize); Binding.wType := DBTYPE_UI2; end; ftSmallint: begin ParamSize := 2; CheckSize(Offset + 8 + ParamSize); value := Param.AsInteger; move(value, PData^.Data[0], ParamSize); Binding.wType := DBTYPE_I2; end; ftAutoInc, ftInteger, ftLargeint: begin ParamSize := 4; CheckSize(Offset + 8 + ParamSize); value := Param.AsInteger; move(value, PData^.Data[0], ParamSize); Binding.wType := DBTYPE_I4; end; ftDateTime: begin ParamSize := 8; CheckSize(Offset + 8 + ParamSize); DateValue := Param.AsDateTime; move(DateValue, PData^.Data[0], ParamSize); Binding.wType := DBTYPE_DATE; end; ftDate, ftTime: begin ParamSize := 8; CheckSize(Offset + 8 + ParamSize); DateValue := Param.AsDate; move(DateValue, PData^.Data[0], ParamSize); Binding.wType := DBTYPE_DATE; end; ftFloat, ftCurrency, ftBCD : begin CheckSize(Offset + 8 + ParamSize); FloatValue := Param.AsFloat; move(FloatValue, PData^.Data[0], ParamSize); Binding.wType := DBTYPE_R8; end; ftBlob, ftGraphic, ftFmtMemo, ftBytes, ftVarBytes, ftString, ftMemo: begin en

- 29 Les sources prsentes sur cette page sont libres de droits et vous pouvez les utiliser votre convenance. Par contre, la page de prsentation constitue une uvre intellectuelle protge par les droits d'auteur. Copyright 2009 Franck SORIANO. Aucune reproduction, mme 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 intrts. Cette page est dpose la SACD.

http://fsoriano.developpez.com/articles/db/oledb/delphi/

Utiliser OLEDB en Delphi par Franck SORIANO

stringValue := Param.AsString; ParamSize := length(stringValue); Binding.cbMaxLen := ParamSize; CheckSize(Offset + 8 + ParamSize); PData^.LengthValue := ParamSize; move(stringValue[1], PData^.Data[0], ParamSize); if (Param.DataType = ftMemo) or (Param.DataType = ftString) then Binding.wType := DBTYPE_STR else Binding.wType := DBTYPE_BYTES; end; ftWideString, ftWideMemo: begin widestringValue := Param.AsWideString; ParamSize := length(widestringValue)*2; Binding.cbMaxLen := ParamSize; CheckSize(Offset + 8 + ParamSize); PData^.LengthValue := length(widestringValue); move(widestringValue[1], PData^.Data[0], ParamSize); if (Param.DataType = ftMemo) or (Param.DataType = ftString) then Binding.wType := DBTYPE_STR else Binding.wType := DBTYPE_BYTES; end; end; inc(offset, ParamSize + 8); end; // A prsent, il ne reste plus qu' dfinir l'accesseur. Cmd := ACmd; Cnt.OleDbCheck((Cmd as IAccessor).CreateAccessor(DBACCESSOR_PARAMETERDATA, Params.Count, @Bindings[0], 0, hAccessor, nil)); Parameters.cParamSets := 1; Parameters.HACCESSOR := hAccessor; Parameters.pData := @FData[0]; end else begin Parameters.cParamSets := 0; Parameters.HACCESSOR := 0; Parameters.pData := nil; end; end;

- 30 Les sources prsentes sur cette page sont libres de droits et vous pouvez les utiliser votre convenance. Par contre, la page de prsentation constitue une uvre intellectuelle protge par les droits d'auteur. Copyright 2009 Franck SORIANO. Aucune reproduction, mme 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 intrts. Cette page est dpose la SACD.

http://fsoriano.developpez.com/articles/db/oledb/delphi/

Utiliser OLEDB en Delphi par Franck SORIANO

Le constructeur lit la collection TParams en entre et initialise la structure Parameters qui sera utilise pour excuter la requte. Une fois la requte excute, la valeur des paramtres de sorti sera automatiquement mise en jour l'intrieur du buffer de TOleDbParams, grce l'accesseur. Il suffira donc de dcoder le buffer pour mettre jour la collection TParams avec les nouvelles valeurs :
procedure TOleDbParams.UpdateParams(Params: TParams); var i : integer; Param : TParam; PData : PFieldData; value : cardinal; DateValue : TDatetime; FloatValue : double; StringValue : string; WidestringValue : widestring; begin if Assigned(Params) and (Params.Count>0) then begin // On parcourt la liste des paramtres pour mettre jour la valeur de paramtres de sorti // en fonction du nouvel tat du buffer. for i := 0 to Params.Count -1 do begin Param := Params[i]; // On ne traite que les paramtres de sorti. Inutile de s'occuper de ceux qui sont en // entre seule. if (Param.ParamType in [ptOutput, ptInputOutput]) and (i<= high(FParamInfo)) then begin PData := @FData[FParamInfo[i].Offset]; if PData.NullStatus = DBSTATUS_S_ISNULL // Cas ou le paramtre vaut NULL then Param.Clear else begin // Si le paramtre ne vaut pas null, il reste copier la valeur du buffer // en fonction du type de donnes case Param.DataType of ftWord, ftBoolean, ftSmallint: // Entier sur 2 octets begin value := 0; move(PData.Data[0], value, 2); Param.AsInteger := value; end; ftAutoInc, ftInteger, ftLargeint: // Entier sur 4 octets begin move(PData.Data[0], value, 4); Param.AsInteger := value; end; ftDate, ftTime, ftDateTime: // Date begin move(PData^.Data[0], DateValue, 8); Param.AsDateTime := DateValue; end; ftFloat, ftCurrency, ftBCD : begin move(PData^.Data[0], FloatValue, 8); Param.AsFloat := FloatValue; end; ftBlob, ftGraphic, ftFmtMemo, ftBytes, ftVarBytes, ftString, ftMemo: begin SetLength(stringValue, PData.LengthValue); move(PData.Data[0], stringValue[1], PData.LengthValue); Param.AsString := stringValue; end; ftWideString, ftWideMemo: begin SetLength(WidestringValue, PData.LengthValue div 2);

- 31 Les sources prsentes sur cette page sont libres de droits et vous pouvez les utiliser votre convenance. Par contre, la page de prsentation constitue une uvre intellectuelle protge par les droits d'auteur. Copyright 2009 Franck SORIANO. Aucune reproduction, mme 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 intrts. Cette page est dpose la SACD.

http://fsoriano.developpez.com/articles/db/oledb/delphi/

Utiliser OLEDB en Delphi par Franck SORIANO

move(PData.Data[0], WidestringValue[1], PData.LengthValue); Param.AsString := WidestringValue; end; end; end; end; end; end; end;

Attention : Selon le provider OLEDB utilis, le buffer ne sera mis jour avec les paramtres de sorti qu'une fois l'excution de la commande compltement termine, et que le jeux de rsultats aura t compltement lu (le Rowset a t libr). C'est notamment le cas avec SQL Server. Ca vient tout simplement de l'implmentation du protocole rseau utilis pour envoyer les donnes au client : Le provider OLEDB dcode le flux rseau au fur et mesure que les donnes sont lues. Or dans le flux TDS, les valeurs des paramtres de sorti sont tous simplement transmises aprs les donnes des SELECT. De sorte qu'on ne peut pas connaitre la valeur de ces paramtres tant qu'on n'a pas finit de lire toutes les donnes renvoyes par le select. Cette approche est trs basique. Les paramtres sont uniquement typs dans TParams partir de leur valeur initiale. Pour un paramtre de sorti, a signifie qu'il faut d'abord initialiser une valeur au paramtre pour pouvoir dimensionner le buffer intermdiaire. Cependant, la technique prsente ici est suffisante pour ce tutoriel sur OleDb. Au final, l'excution de la requte paramtre s'effectue de la faon suivante :
// Si la requte est paramtre, il faut initialiser les valeurs des paramtres. C'est le rle // de la classe TOleDbParams. Si elle n'attend pas de paramtre, TOleDbParams dfinit un jeu // de paramtres vide. OleDbParams := TOleDbParams.Create(Params, Cmd, self); try OleDbCheck(cmd.Execute(nil, IID_IRowset, OleDbParams.Parameters, nil, @unknown)); if Assigned(unknown) then begin RowSet := unknown as IRowSet; unknown := nil; // La source de donnes vient de rendre la main. On peut lire les donnes. ds := TOleDbDataSet(CreateDataSet); // On instancie le DataSet qui sera retourn. ds.LoadFromRowSet(RowSet, self, FetchSize); // Lecture des donnes. RowSet := nil; end else ds := nil; // Aprs l'excution de la requte on met jour les valeurs des paramtres de sorti. OleDbParams.UpdateParams(Params); finally OleDbParams.Free; end;

- 32 Les sources prsentes sur cette page sont libres de droits et vous pouvez les utiliser votre convenance. Par contre, la page de prsentation constitue une uvre intellectuelle protge par les droits d'auteur. Copyright 2009 Franck SORIANO. Aucune reproduction, mme 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 intrts. Cette page est dpose la SACD.

http://fsoriano.developpez.com/articles/db/oledb/delphi/

Utiliser OLEDB en Delphi par Franck SORIANO

II-F - Prparer les Requtes


OLEDB, comme beaucoup d'API permet de prparer les requtes SQL avant de les excuter. Lorsqu'on prpare une requte, on commence par envoyer le texte de la requte au SGBD. Ce dernier va la compiler et mmoriser son plan d'excution pour renvoyer un handle au client. Ensuite, lors de chaque excution, le client n'aura qu' indiquer ce handle et le SGBD pourra faire l'excution sans avoir recompiler et recalculer le plan d'excution. Lorsque le client a fini d'utiliser une requte, il annule la prparation pour que le serveur dtruise le plan d'excution ainsi cr. Pour prparer une commande OLEDB, il suffit d'appeler ICommandPrepare.Prepare partir d'une commande dj cre, puis d'appeler ICommandPrepare.Unprepare lorsque la commande n'est plus utilise. Cependant j'ai choisi de ne pas l'implmenter dans TOleDbConnection, principalement pour deux raisons : Tout d'abord, on comprend bien que prparer une commande ncessite des changes d'informations supplmentaires avec le SGBD. La prparation d'une commande a un cot. Ce cot sera thoriquement rentabilis sur les excutions suivantes de la commande. Ce qui signifie que pour que la prparation de la commande prsente un intrt, il faut que cette dernire soit excute plusieurs fois. La documentation de SQL Server dit mme que si la commande n'est pas excute au moins 3 fois, la prparation dgrade les performances. Or dans TOleDbConnection on cre une nouvelle commande chaque appel OpenSQL ou ExecSQL. Cette faon de travailler est dj incompatible avec la prparation des commandes. Ensuite, avec SQL Server (qui rappelons le est la cible principale de TOleDbConnection), prparer les requtes ne sert gnralement rien. En effet, SQL Server utilise un mcanisme de mise en cache des plans d'excution qui lui permet de se rendre compte automatiquement qu'il possde dj un plan d'excution pour une requte SQL pralablement excute, ce qui lui vite de la recompiler. Pour peu qu'on utilise en plus des requtes paramtres (soit manuellement, soit avec l'option PARAMETERIZATION FORCED sur la base de donnes), prparer les requtes serait vraiment contre performant.

- 33 Les sources prsentes sur cette page sont libres de droits et vous pouvez les utiliser votre convenance. Par contre, la page de prsentation constitue une uvre intellectuelle protge par les droits d'auteur. Copyright 2009 Franck SORIANO. Aucune reproduction, mme 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 intrts. Cette page est dpose la SACD.

http://fsoriano.developpez.com/articles/db/oledb/delphi/

Utiliser OLEDB en Delphi par Franck SORIANO

II-G - Gestion des Transactions


A prsent, il ne reste plus qu' voir la gestion des transactions avec OLEDB. C'est trs simple puisqu'il suffit d'appeler les mthodes de l'interface ITransactionLocal. On peut obtenir l'interface ITransactionLocal partir de la session en cours, en faisant simplement un QueryInterface (ou avec l'oprateur AS en Delphi). Attention cependant, cette interface est une interface facultative dans les spcifications OLEDB. Il ce peut donc qu'elle ne soit pas disponible si la source de donnes ne gre pas les transactions.

II-G-1 - Dmarrer une transaction : StartTransaction


Pour dmarrer une nouvelle transaction, on appelle la mthode StartTransaction :
// On appelle la mthode StartTransaction de l'interface OLEDB OleDbCheck(FTransaction.StartTransaction(ISOLATIONLEVEL_READCOMMITTED, 0, nil, nil));

On remarquera qu'on prcise le niveau d'isolation de la transaction pour chaque transaction. Nous allons encapsuler cet appel dans la classe TCustomOleDbConnection pour fournir une mthode publique StartTransaction :
procedure TCustomOleDbConnection.StartTransaction; begin CheckConnected; // FTransaction vaut nil si le provider OLEDB ne gre pas les transactions if Assigned(FTransaction) then begin try // On trace le dbut de la transaction. SQLLogger.TraceStartTransaction(TransactionTime); // On appelle la mthode StartTransaction de l'interface OLEDB OleDbCheck(FTransaction.StartTransaction(ISOLATIONLEVEL_READCOMMITTED, 0, nil, nil)); except on e:exception do begin SQLLogger.TraceException(e); raise; end; end; end; end;

- 34 Les sources prsentes sur cette page sont libres de droits et vous pouvez les utiliser votre convenance. Par contre, la page de prsentation constitue une uvre intellectuelle protge par les droits d'auteur. Copyright 2009 Franck SORIANO. Aucune reproduction, mme 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 intrts. Cette page est dpose la SACD.

http://fsoriano.developpez.com/articles/db/oledb/delphi/

Utiliser OLEDB en Delphi par Franck SORIANO

II-G-2 - Valider une transaction : Commit


La validation d'une transaction en cours s'effectue videmment avec la mthode Commit.
OleDbCheck(FTransaction.Commit(false, XACTTC_SYNC, 0));

Lors de l'appel du commit, il faut prciser si on veut conserver le niveau d'isolation en cours ou revenir au niveau par dfaut configur pour la connexion. Le deuxime paramtre sert grer le commit deux phases pour les transactions distribues ainsi que le commit asynchrone. Ce n'est pas l'objet de ce tutoriel, on se contentera d'utiliser la valeur XACTIC_SYNC pour faire un commit standard. Le dernier paramtre est une valeur rserve qui doit toujours valoir 0. Il ne reste plus qu' ajouter une mthode Commit dans TCustomOleDbConnection :
procedure TCustomOleDbConnection.Commit; begin CheckConnected; if Assigned(FTransaction) then begin try SQLLogger.TraceCommitTransaction(TransactionTime); OleDbCheck(FTransaction.Commit(false, XACTTC_SYNC, 0)); except on e:exception do begin SQLLogger.TraceException(e); raise; end; end; end; end;

- 35 Les sources prsentes sur cette page sont libres de droits et vous pouvez les utiliser votre convenance. Par contre, la page de prsentation constitue une uvre intellectuelle protge par les droits d'auteur. Copyright 2009 Franck SORIANO. Aucune reproduction, mme 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 intrts. Cette page est dpose la SACD.

http://fsoriano.developpez.com/articles/db/oledb/delphi/

Utiliser OLEDB en Delphi par Franck SORIANO

II-G-3 - Annuler une transaction : Abort


Pour faire un rollback, il faut utiliser la mthode Abort :
OleDbCheck(FTransaction.Abort(nil, false, false));

Nous allons l'encapsuler dans TCustomOleDbConnection avec la mthode Rollback :


procedure TCustomOleDbConnection.Rollback; begin // On vrifie que la connexion est bien ouverte. CheckConnected; if Assigned(FTransaction) then begin try // On trace le rollback SQLLogger.TraceRollbackTransaction(TransactionTime); // On appelle Abort pour annuler la transaction. OleDbCheck(FTransaction.Abort(nil, false, false)); except on e:exception do begin SQLLogger.TraceException(e); raise; end; end; end; end;

- 36 Les sources prsentes sur cette page sont libres de droits et vous pouvez les utiliser votre convenance. Par contre, la page de prsentation constitue une uvre intellectuelle protge par les droits d'auteur. Copyright 2009 Franck SORIANO. Aucune reproduction, mme 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 intrts. Cette page est dpose la SACD.

http://fsoriano.developpez.com/articles/db/oledb/delphi/

Utiliser OLEDB en Delphi par Franck SORIANO

III - Evaluation des rsultats et exemples d'utilisations


Nous avons termin l'implmentation des classes TOleDbConnection et TOleDbDataset. Voyons prsent quelques exemples d'utilisation qui nous permettrons de tester nos accs base de donnes et valuer les performances obtenues avec OleDb.

III-A - Environnement de tests


Pour effectuer les tests, nous allons travailler avec une instance SQLExpress installe en local. Les temps d'excution des requtes et de lecture des rsultats seront directement dpendants de notre implmentation et non lis au rseau. Nous allons travailler sur la base de donnes d'exemple de SQL 2005 : AdventureWorksLT. Cette dernire peut tre tlcharge sur Codeplex l'adresse suivante : http://www.codeplex.com/MSFTDBProdSamples/Release/ProjectReleases.aspx?ReleaseId=4004 La classe TOleDbConnection a t instrumente avec ETW. Nous allons regarder quelques cas d'utilisation simple et controller les traces gnres dans ETWSqlProfiler.

III-B - Connexion/Dconnexion
Il suffit de renseigner la chane de connexion et de dfinir la proprit Connected True :
var cnt : TOleDbConnection; begin // Cration d'un nouvel objet TOleDbConnection cnt := TOleDbConnection.Create(nil); // Ici on demande la construction d'une nouvelle chane de connexion. cnt.ConnectionString := TOleDbConnection.PromptConnexionString('', Application.MainFormHandle); // Enfin, on ouvre la connexion : cnt.Connected := true; // Maintenant on ferme la connexion. cnt.Connected := false;

- 37 Les sources prsentes sur cette page sont libres de droits et vous pouvez les utiliser votre convenance. Par contre, la page de prsentation constitue une uvre intellectuelle protge par les droits d'auteur. Copyright 2009 Franck SORIANO. Aucune reproduction, mme 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 intrts. Cette page est dpose la SACD.

http://fsoriano.developpez.com/articles/db/oledb/delphi/

Utiliser OLEDB en Delphi par Franck SORIANO

Si on excute cet exemple, on obtient la trace suivante :

On peut constater que les lignes vertes LOGIN et LOGOUT indiquent immdiatement que la connexion a t ouverte et ferme. La colonne Delta indique le temps coul en millisecondes depuis la ligne prcdente dans la trace. Comme chaque vnement a t prcd d'un message d'information indiquant la chane de connexion concerne, on peut en dduire le temps d'ouverture de la connexion (moins de 4 ms) et le temps de dconnexion (moins de 1 ms). En cas d'erreur au moment de la connexion la base, l'exception est galement automatiquement enregistre dans la trace :

- 38 Les sources prsentes sur cette page sont libres de droits et vous pouvez les utiliser votre convenance. Par contre, la page de prsentation constitue une uvre intellectuelle protge par les droits d'auteur. Copyright 2009 Franck SORIANO. Aucune reproduction, mme 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 intrts. Cette page est dpose la SACD.

http://fsoriano.developpez.com/articles/db/oledb/delphi/

Utiliser OLEDB en Delphi par Franck SORIANO

III-C - Excuter une requte


Une fois la connexion ouverte, on peut excuter une requte avec un simple appel la mthode OpenSQL :
var ds : TDataSet; begin // On excute une requte SELECT quelconque. ds := cnt.OpenSQL('select * from SalesLT.Customer'); // On travaille en mode dconnect. Une fois la requte excute, on peut // fermer la connexion et continuer travailler sur le dataset. // Le dataset renvoy est un dataset bi-directionnel qu'on peut trs bien // afficher dans une DBGrid ! DBGrid1.Datasource.DataSet := nil; DBGrid1.Datasource.DataSet := ds;

Ici, on commence par ouvrir la connexion la base, puis on lit la table SalesLT.Customer dans sa totalit. OpenSQL renvoit un dataset en mmoire totalement dconnect de la base de donnes. Ca signifie qu'on peut faire ce qu'on veut du dataset retourn : On peut garder autant de dataset ouvert qu'on le souhaite et on peut mme fermer la connexion la base. Le dataset reste toujours valide. Il peut mme servir de table temporaire en mmoire. De plus, contrairement dbExpress, le dataset retourn est bidirectionnel. Ca signifie qu'on peut l'afficher dans une grille. Voyons prsent la trace gnre l'excution :

L'excution de la requte a gnr cinq lignes dans la trace : La premire ligne correspond au dbut de la requte. On voit la requte qui est envoye au SGBD. Lorsque le SGBD a fini de traiter la requte, il rend la main et on commence lire les rsultats. Cet instant est galement trac avec l'vnement Debut Fetch . Le Fetch en lui-mme prend un certain temps. Lorsqu'il est termin, un nouvel vnement indique la fin de la lecture des donnes : Fin Fetch . Enfin, l'excution de la requte est termine. Un nouvel vnement dans la trace indique la fin de OpenSQL, en rappelant la requte excute et le nombre de lignes lues.

Examinons maintenant les temps d'excution. Comme on peut le voir, l'excution de la requte sur le SGBD a dure moins de 1 ms. Par contre ensuite, la lecture des rsultats de la requte a pris environ 4 ms pour lire 440 lignes. De plus, on voit que chaque ligne lue occupe 1598 octets dans le dataset. Cet exemple montre clairement que de nos jours, l'excution d'une requte simple sur un SGBD est trs rapide. Par contre on passe ensuite l'essentiel du temps de traitement lire les rsultats de la requte. Or la dure du fetch est troitement lie l'API utilise. D'o l'importance de choisir une API performante.

- 39 Les sources prsentes sur cette page sont libres de droits et vous pouvez les utiliser votre convenance. Par contre, la page de prsentation constitue une uvre intellectuelle protge par les droits d'auteur. Copyright 2009 Franck SORIANO. Aucune reproduction, mme 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 intrts. Cette page est dpose la SACD.

http://fsoriano.developpez.com/articles/db/oledb/delphi/

Utiliser OLEDB en Delphi par Franck SORIANO

Si nous n'avions pas travailler en local, les temps de fetch aurraient t encore plus importants.

III-D - Excuter une requte paramtre


Pour excuter une requte paramtre, il suffit de fournir une collection TParams en paramtres OpenSQL. Dans la requte, on utilise des "?" la place des paramtres. Il n'est pas possible de les nommer. C'est le standard prvu par Microsoft et qu'on retrouve aussi bien dans OLEDB que dans ADO. Par exemple, si on veut obtenir la liste des produits mis en vente avant le 01/01/2000. On va effectuer une requte paramtre en indiquant la date en paramtre :
var Params : TParams; Param : TParam; ds : TDataSet; begin // On excute une requte SELECT avec un paramtre DATE. Params := TParams.Create; try TParam(Params.Add).AsDateTime := EncodeDate(2000, 1, 1); ds := cnt.OpenSQL('select * from SalesLT.Product where SellStartDate < ?', Params); finally Params.free; end;

III-E - Messages d'informations


Certaines commandes SQL retournent des messages qui ne sont pas des datasets. On a vu que ces messages d'information remontent sous la forme d'erreurs d'excution. La classe TOleDbConnection isole ces messages et les mmorise dans la proprit InfoMessages. Par exemple, si on excute une commande PRINT :
// On excute une commande PRINT. cnt.ExecSQL('PRINT ''Message de test'''); // Le message du PRINT est retourn dans InfoMessages. ShowMessage(cnt.InfoMessages.Text);

Il suffit alors de lire la proprit InfoMessages pour lire le message renvoy. Cette proprit est rinitialise chaque excution d'une commande SQL.

- 40 Les sources prsentes sur cette page sont libres de droits et vous pouvez les utiliser votre convenance. Par contre, la page de prsentation constitue une uvre intellectuelle protge par les droits d'auteur. Copyright 2009 Franck SORIANO. Aucune reproduction, mme 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 intrts. Cette page est dpose la SACD.

http://fsoriano.developpez.com/articles/db/oledb/delphi/

Utiliser OLEDB en Delphi par Franck SORIANO

III-F - Lire un fichier EXCEL


TOleDbConnection a t conue pour SQL Server. Cependant, le code reste valable pour n'importe quel provider OLEDB. Par exemple, on peut utiliser le provider Microsoft Jet 4.0 pour lire un fichier EXCEL. Il suffit d'utiliser une chane de connexion du type :
Provider=Microsoft.Jet.OLEDB.4.0;Data Source=<Fichier excel ouvrir>;Extended Properties=Excel 8.0

Ensuite, on peut faire une requte SQL sur le classeur :


select * from [Feuil1$]

IV - Conclusion
Dans cet article, nous avons vu les bases de OLEDB. Nous avons vu comment utiliser cette API bas niveau afin d'excuter des requtes SQL sur une base de donnes. De premier abord, OLEDB est loin d'tre simple. Mais si on s'y intresse de plus prs, il suffit de comprendre le fonctionnement du binding et des accesseurs. Aprs, le reste n'est pas plus compliqu qu'utiliser ADO. Lorsqu'on utilise OLEDB directement, on peut obtenir des performances vraiment excelentes. Par exemple, on a pu excuter une requte renvoyant tous les clients de la base AdventureWorksLT (440 lignes en tout) en 4 ms. Dans le prochain article nous verrons comment OLEDB et SQL Server nous permettent d'effectuer des chargements de donnes en blocs. On pourrait ainsi insrer massivement des donnes dans une table, directement depuis une application Delphi, la vitesse d'un bcp, DTS, ou autre SSIS.

V - Rfrences
Le Tracing avec Event Tracing for Windows (ETW) : http://fsoriano.developpez.com/articles/etw/delphi/ Comparatif des architectures des API d'accs aux donnes : http://fsoriano.developpez.com/articles/db/comparatifapi/ Dvelopper un DataSet en mmoire : http://fsoriano.developpez.com/articles/db/dataset/delphi La documentation OLEDB sur MSDN : http://msdn.microsoft.com/en-us/library/ms722784(VS.85).aspx La base de donnes d'exemple AdventureWorksLT : http://www.codeplex.com/MSFTDBProdSamples/Release/ProjectReleases.aspx?ReleaseId=4004

VI - Remerciements
Je remercie particulirement Nono40 pour sa relecture et conseils aviss !

- 41 Les sources prsentes sur cette page sont libres de droits et vous pouvez les utiliser votre convenance. Par contre, la page de prsentation constitue une uvre intellectuelle protge par les droits d'auteur. Copyright 2009 Franck SORIANO. Aucune reproduction, mme 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 intrts. Cette page est dpose la SACD.

http://fsoriano.developpez.com/articles/db/oledb/delphi/