delphi3000.com - the free delphi knowledge platform
delphi3000.com - the free delphi knowledge platform
Have a look at your member-status

connecting people's knowledge


  - Recent ArticlesRSS feed for Recent Articles on delphi3000.com
  - List of All Articles
  - Top Viewed Articles
  - Articles (+Attachem.)
  - Articles Of Interest
  - Categories
  - Top Uploader
  - Search
  - Index

  - My Home
  - Submit an Article
  - My Articles
  - My Personal Data
  - My Bookmarks
  - Activities
  - Login/Logout

  - Sign Up
  - Why Sign Up
  - Newsletter

  - Press
  - Advertise

  - Contact
  - Feedback



Community
Borland
ClubeDelphi
Dr. Bob
UK-BUG
Delphi Meetings
Planeta Delphi








Share this article with friendsShare this article with friends
Rate this articleRate this article - to keep the quality of delphi3000.com !
Comment this article or read through previous comments (0)


Simple guide to ADOGo to Hans Pieters's websiteFormat this article printer-friendly!Bookmark function is only available for registered users!
a flexible and high performance connection to a range of databases
Product:
Delphi 5.x (or higher)
Category:
ADO/OLE-DB
Skill Level:
Scoring:
Last Update:
11/29/2001
Search Keys:
delphi delphi3000 article borland vcl code-snippet ADO DATABASES UDL
Times Scored:
12
Visits:
12927
Uploader: Hans Pieters
Company: IHP Software France
Reference: N/A
 
Question/Problem/Abstract:
After two years of working with ADO and with numerous applications, running day after day in a multiuser, administrative environnement , I am more content with ADO than ever. I work mainly with MS SQL Server 2000, MS Access and sometimes with good old Foxpro. I don't implement big applications (100 or more users with zillions of records) and on my scale ADO works really perfect.

A year ago I wrote some articles about ADO. Those articles are outdated by now, but are still read. That is why I wrote this article to replace some of the previous articles. I will guide you through the making of a complete basic ADO application. Let us start.

Answer:



If you want to use ADO you will mainly work with these 3 components:
1. TADOConnection : handling the connection to a database,
2. TADOQuery : to extract a dataset,
3. TADOCommand : to manipulate the database directly.

Direct use of the connectionstring property of the ADO Connect component is against everything ADO stands for: a flexible and high performance connection to a range of databases.  The way to handle the connection data is via a ini-file (*.INI) or a MS Universal Data Link file (*.UDL). An important aspect of a good application is flexibility, so the best strategy is to cater for both.

Flexibility is also the reason for implementing a priority in looking for the connectiondata for a user requesting the connection. If user JOHND starts application SHOWDATA the application SHOWDATA should look first for the ini-file JOHND.INI and after that for SHOWDATA.INI. Without an ini-file my applications should terminate.

The second step is the connection file. SHOWDATA should look first for JOHND.UDL, after that for SHOWDATA.UDL and after that assume that the ini file contains the connection information.
  
The beauty of an ini-file is that you can change the (connection)information even with Notepad, but you have to enter them manually and ... connection information should be without typos. The UDL file is easier in that respect becuase you will use a wizard.
The UDL file can be created one way or another, but one way 'allways' works. If you want to create myapp.udl, create that file - as an empty file - with Wordpad/Notepad. Open it with Windows Explorer and you are in the UDL-wizard.

Starting with an empty mainunit.mainform and an empty dataunit.dataform.
Drop on the dataform:
a TADOconnection (called ADO_conn),
a TADOcommand (called ADO_comm),
a TADOquery (called AQ_test) and
a TDataSource (called DSAQ_test).
Enter:
'ADO_conn' in the connection property of ADO_comm and AQ_test.
'AQ_test' in the dataset property of in DSAQ_test.
'Select * from mytable' in the SQL property of AQ_test
(mytable is your test table in your test database)

----------------------------------------------------------------------
Job 1: complete mainunit.
----------------------------------------------------------------------

MAKE SURE THAT THE USES LIST OF THE INTERFACE SECTION COMPRISES:
... db, ADOdb, inifiles;

CREATE THE FOLLOWING VARIABLES:
   inifilename, connectionfilenam, currentusername: string;
   appinifle: tInifile;
   trnsproc: boolean;
   procedure mainformcreate_part2;

IN USES - LIST IN THE IMPLEMENTATION SECTION:
   uses dataunit;

WRITE FORMCREATE EVENT FOR THE MAINFORM:

procedure Tmainform.FormCreate(Sender: TObject);
begin
  mainunit.currentusername := mainunit.GetCurrentUserName;
  mainunit.trnsproc := false;
  mainunit.inifilename := mainunit.ExtractInifilename;
  mainunit.connectionfilename := mainunit.ExtractConnectionfilename;
  mainunit.appinifile := TIniFile.Create(mainunit.inifilename);
  mainunit.mainformcreate_part2;
end;

procedure mainformcreate_part2;
begin
  {
  the code that changes per application
  }
end;

SUPPORTING FUNCTIONS IN THE MAINFORM (Observe the priority issue)

function ExtractInifilename: string;
begin
  {Priority  1. USERNAME.INI  
             2. APPNAME.INI  
             3. TERMINATE APPLICATION}
  result := ExtractFilePath(ParamStr(0)) +
            mainunit.currentusername + '.ini';
  if not FileExists(result) then
  begin
    result := ChangeFileExt(Paramstr(0), '.ini');
    if not FileExists(result) then
    begin
      showmessage('Fatal: No INI-file found');
      application.Terminate;
    end;
  end;
end;

function ExtractConnectionfilename: string;
begin
  {Priority 1. APPNAME.UDL  
            2. APPNAME.INI  
            3. USERNAME.INI}
  result := ChangeFileExt(Paramstr(0), '.udl');
  if not FileExists(result) then
  begin
    result := ChangeFileExt(Paramstr(0), '.ini');
    if not FileExists(result) then result := inifilename
  end;
end;

function GetCurrentUserName: string;
var
  Len: Cardinal;
begin
  Len := 255; { includes one char for null terminator }
  SetLength(Result, Len - 1);
  if GetUserName(PChar(Result), Len)
    then SetLength(Result, Len - 1)
  else result := 'LocalUser';
end;

----------------------------------------------------------------------
Job 2: complete dataunit - connection
----------------------------------------------------------------------

unit dataunit;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, FileCtrl,
  Forms, Dialogs,Db, ADODB, inifiles, clipbrd, DBTables;

type
  TDataform = class(TDataModule)
    ADO_conn: TADOConnection;
    ADO_comm: TADOCommand;
    AQ_TEST: TADOQuery;
    DSAQ_TEST: TDataSource;
    procedure DataModuleCreate(Sender: TObject);
    {
    etc...
    }
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Dataform: TDataform;

procedure datamodulecreate_part2;
procedure transproc(job: char);
function exec_command(commandstring: string;
  do_setcommstring: boolean = true; do_parsesql: boolean = true;
  do_promptparms: boolean = true; do_localtp: boolean = true;
  do_exec: boolean = true; testmode: boolean = false): integer;

implementation

uses mainunit;

{$R *.DFM}

procedure TDataform.datamoduleCreate(Sender: TObject);
var
  connlist: tstringlist;
  connstr, errmess, temp: string;
  countvar: integer;
begin
  try
    {we want the decision to invoke login dialog in the personal ini-
     file. Gives greater flexibility per user}
    temp := mainunit.appinifile.Readstring
            ('STARTAPPLICATION', 'LoginPrompt1', 'True');
    ado_conn.LoginPrompt := (uppercase(temp) = 'TRUE');

    if ExtractFileExt(mainunit.Connectionfilename) = '.UDL' then
    begin
      errmess := 'UDL-file problem - Connection 1';
      connstr := Format('File Name=%s',[mainunit.Connectionfilename]);
    end
    else
    begin
      errmess := 'INI-file problem - Connection 1';
      connlist := tstringlist.create;
      appinifile.ReadSectionValues('CONNECTSTRING1', connlist);
      for countvar := 0 to connlist.count - 1
        do connstr := connstr + connlist.strings[countvar];
      Freeandnil(connlist);
    end;

    dataform.ADO_conn.connected := false;
    dataform.ADO_conn.connectionstring := connstr;
    dataform.ADO_conn.connected := true;
    
    temp := appinifile.Readstring(
            'STARTAPPLICATION', 'Transactiemachanism', 'None');
    mainunit.trnsproc := (uppercase(temp) = 'AUTOMATIC');

  except
    Freeandnil(connlist);
    showmessage('Fatal: '+ errmess) ;
    application.Terminate;
  end;
  datamodulecreate_part2;
end;

procedure datamodulecreate_part2;
var
  errmess: string;
begin
  try
    {do here all other things you have to do ....}
    with dataform do
    begin
    {open here all datasets }
    
    errmess := 'Cannot open dataset AQ_TEST ';
    aq_test.open;
    
    end;
  except
    showmessage('Fatal: '+ errmess) ;
    application.Terminate;
  end;
end;

Remarks:
I have split the datamodulecreate (and the mainformcreate). The first part is the same for all apps while the second part is variable. I replaced my message system in this article with showmessage (in reality unacceptable!)

You could even go a step further. In my article 'Let User Create an UDL-file from within your App.' I describe how - within your application - to interface the user with the UDL-file wizard and write the UDL file after the user hits the OK button. Implement this in the except part of the datamodulecreate procedure and the whole stuff is automated.

If you only want data-aware components on you form, then don't read further because this all you have to do! Put some data aware components on the mainform and compile.
If you want to know  a bit more about the connection string, go to the end of this article.

----------------------------------------------------------------------
Job 3: complete dataunit - transaction processing
----------------------------------------------------------------------

As I never use batch update via the dataset  - but always with SQL statements via the command object - I do not need the transaction processing mechanism applied on single-record-user-operations via the dataset. Makes life much easier.

When updating the database directly, that is directly via the TADOcommand object or via my Comm_Exec function, the use of the transaction processing mechanism is a must.

The procedure transproc(job: char) interfaces to the transaction processing mechanism.  The working of the procedure is clear, I suppose.

procedure transproc(job: char);
begin
  with dataform.ado_conn do
    case job of
      'B':
        begin
          dataunit.transproc('T');
          begintrans;
        end;
      'C':
        if intransaction then committrans
        else showmessage('Nothing to commit - Connection 1');
      'R':
        if intransaction then rollbacktrans
        else showmessage('Nothing to rollback - Connection 1');
      'T':
        if intransaction then
        begin
          rollbacktrans;
          showmessage('Had to rollbach active transaction - Connection 1');
        end;
    else showmessage('Wrong request for connection 1');
    end;
end;

The  exec_command function is also not difficult to understand. An errornumber is negative when the dataset was empty. The purpose of the 'do_localtp' parameter is that you can bring two or more commands in a single transaction.
Example:

      transproc('B');
      if exec_command(... False....) = 0 then
      begin
        if exec_command(... False ...)= 0  
        then transproc('C') else transproc('R');
      end
      else transproc('R') ;


function exec_command(commandstring: string;
  do_setcommstring: boolean = true; do_parsesql: boolean = true;
  do_promptparms: boolean = true; do_localtp: boolean = true;
  do_exec: boolean = true; testmode: boolean = false): integer;
var
  excomm_error, excomm_empty, teller: integer;
  parm: string;
begin
  if length(trim(commandstring)) = 0
    then excomm_empty := -1
  else excomm_empty := 1;

  with dataform.ado_comm do
  begin
    excomm_error := 0;
    result := 0;
    try
      excomm_error := 1;
      if do_localtp then transproc('T');
      excomm_error := 2;
      if testmode then clipboard.settextbuf(pchar(commandstring));
      excomm_error := 3;
      if do_setcommstring then commandtext := commandstring;
      excomm_error := 4;
      if do_parsesql then parameters.parsesql(commandstring, true);
      excomm_error := 5;
      if (not do_promptparms) and (not do_exec) then exit;
      if do_promptparms then
      begin
        excomm_error := 6;
        if parameters.count > 0
          then begin
          for teller := 0 to parameters.count - 1 do
          begin
            parm := '';
            if not inputquery(
               'Enter parameter',parameters.items[teller].name, parm) then
            begin
              Showmessage('Aborted by user - No parameters');
              excomm_error  := 7;
              result := excomm_error * excomm_empty;
              exit;
            end;
            parameters[teller].value := parm;
          end;
        end;
      end;
      if do_exec then
      begin
        excomm_error := 8;
        if (mainunit.trnsproc) and (do_localtp) then transproc('B');
        excomm_error := 9;
        execute;
        if (mainunit.trnsproc) and (do_localtp) then transproc('C');
      end;
      excomm_error := 0 ; {task completed/no error}
    except
      if (do_localtp) then transproc('R');
    end;
  end;
  result := excomm_error * excomm_empty;
end;
end.

----------------------------------------------------------------------
Example of the use of the command object
----------------------------------------------------------------------

General advice: don't try to make a command string with variables and/or fieldvalues by 'knitting' everything into a single string. In simple examples it looks fine, but in real life there is not much that simple. Use always parameters instead.

Procedure
Z1: We work directly on the ADO command object.
    The values are provided in the program (one way or another).
    We have to arrange the transaction processing mechanism.
Z2: We work with exec_comm function (see above).
    The values are provided in the program.
    This is the practical way of working.  
Z3: We work with exec_comm function (see above).
    The values are prompted.
Z1 and Z2 are equivalent. Z3 is different.

procedure Z1;
var
  number,title,author,commtext: string;
begin
  number := '1-55622-758-2' ;
  title  := 'Developers Guide to ADO' ;
  author := 'Fedorov and Elmanove';

  commtext := 'insert into table1 (ISBN,TITLE,AUTHOR) ' +
              'values (:isbnnumber,:booktitle,:author)' ;

  dataunit.ado_comm.commandtext := commtext ;
  dataunit.ado_comm.parameters.parsesql(commtext,true);
  
  dataunit.ado_comm.parameters[0].value := number ;
  dataunit.ado_comm.parameters[1].value := title ;
  dataunit.ado_comm.parameters[2].value := author ;
  
  try
    transproc('B');  
    dataunit.ado_comm.execute;
    transproc('C');
  except
    showmessage('Transaction aborted');
    transproc('R');    
  end;
end;

procedure Z2;
var
   number,title,author,commtext: string;
begin
  number := '1-55622-758-2' ;
  title  := 'Developers Guide to ADO' ;
  author := 'Fedorov and Elmanove';

  commtext := 'insert into table1 (ISBN,TITLE,AUTHOR) ' +
              'values (:isbnnumber,:booktitle,:author)' ;

  if dataunit.exec_comm(commtext,true,true,false,false,false,false)= 0 then
  {adding the command string and parsing}
  begin
     dataunit.ado_comm.parameters[0].value := number ;
     dataunit.ado_comm.parameters[1].value := title ;
     dataunit.ado_comm.parameters[2].value := author ;
     dataunit.exec_comm('',false,false,false,true,true,false);
     {execute the command with transaction procesing}
  end;
end;



procedure Z3;
var
  commtext: string;
begin
  commtext := 'insert into table1 (ISBN,TITLE,AUTHOR) ' +
              'values (:isbnnumber,:booktitle,:author)' ;

  dataunit.exec_comm(commtex)
  { the whole buch incl. prompting the parameters}
end;

----------------------------------------------------------------------
The connectionstring
----------------------------------------------------------------------
The connection part of the ini-file and the udl-file are in principle the same. The most obvious difference is, that the udl-file inserts a space between to udl-file inserts  

Example of a ini-file to an Access database NOPDATA.MDB, residing in the folder F:\TEST\NOP\

[CONNECTSTRING]
Provider=Microsoft.Jet.OLEDB.4.0;
Data Source=F:\TEST\NOP\NOPDATA.MDB;
Mode=Read|Write|Share Deny None;
Persist Security Info=False

The same connection in a udl-file is:

[oledb]
; Everything after this line is an OLE DB initstring
Provider=Microsoft.Jet.OLEDB.4.0;
Data Source=F:\TEST\NOP\NOPDATA.MDB;
Mode=Read|Write|Share Deny None;
Persist Security Info=False

A udl-file to a SQL server 2000 database is:

[oledb]
; Everything after this line is an OLE DB initstring
Provider=SQLOLEDB.1;
Persist Security Info=False;
User ID=sa;
Initial Catalog=DEMOSQLBUILDER;
Data Source=hanspiet

It is easy to learn what should be in to connection string for your favourite database by doing the following:
Wordpad: make empty TEST.TXT and rename to TEST.UDL
Explorer: open TEST.UDL
Build an udl-file to your database. Use Help if needed.
Test connection and save.
Wordpad: Rename TEST.UDL in TEST.TXT and open with Wordpad.
You could eventually cut and paste the connectionstring  
         into an ini-file.





Please rate this article!
Skill level:
BeginnerExpert

Useful:
No!Very!

Overall rating:
PoorExcellent



Comments to this article
Write a new comment













 
Sign up to consume product discounts for Bronze memberships !

read more


   


  Community Ad of
C.A. Longen
 
   














 







     
  Copyright © 2000 - 2007 delphi3000.com - All rights reserved. Terms of use. || Privacy
delphi3000.com is a service by bluestep.com IT-Services GmbH (Vienna)