MS Access/es

From Lazarus wiki

English (en) español (es) français (fr) русский (ru)

Portal de base de datos

Referencias:

Tutorias/artículos prácticos:

Bases de datos

Advantage - MySQL - MSSQL - Postgres - Interbase - Firebird - Oracle - ODBC - Paradox - SQLite - dBASE - MS Access - Zeos

   Esta página explica la forma de utilizar una base de datos de Microsoft Access haciendo uso de ODBC y los componentes integrados de SQLDB de FPC.


Detalles de MS Access

UsePrimaryKeyAsKey

   En las propiedades de TSQLQuery será necesario poner el valor de UsePrimaryKeyAsKey a falso.

Obtener la clave primaria autonumérica del elemento recién insertado

   Después de insertar datos, Access 2000 y superiores permiten obterner la clave primaria autonumérica recién insertada utilizando esta consulta:

SELECT @@IDENTITY

Controladores de ODBC

Windows

   Hay 2 controladores ODBC diferentes para Microsoft Access:

  • el controlador antiguo que solo puede usar archivos con formato .mdb, su nombre es "Microsoft Access driver (* .mdb)". Incluido en muchas versiones de Windows (desde Windows 2000 hasta hoy incluyendo Vista); descargable para versiones anteriores de Windows como parte de los componentes de MDAC o componentes de Jet. 32 bit solamente
  • el nuevo controlador que puede acceder a los archivos .mdb y .accdb, su nombre es "Microsoft Access driver (* .mdb, * .accdb)". Se puede descargar como "Microsoft Access Database Engine 2010 Redistributable"; disponible en 32 y 64 bit.

   Como de costumbre, cuando compilamos Lazarus/FPC a 32 bits usaremos controladores ODBC de 32 bits. Cuando compilemos Lazarus/FPC a 64 bits, usaremos controladores ODBC de 64 bits.

Unix/Linux

   El proyecto mdbtools ofrece un soporte limitado para MS Accesss en linux. Incluye un controlador ODBC que puede ser utilizado. Para obtenerlo deberemos instalar paquetes como mdbtools, mdbtools-dev y mdbtools-gmdb

   Al menos en Debian, el nombre del controlador ODBC es "MDBTools".

Instrucciones DSN basadas en archivos

   Un archivo DSN es simplemente un archivo donde se escriben las configuraciones de conexión. La razón para tener un archivo DSN es por si deseamos distribuir una conexión de fuente de datos a múltiples usuarios en diferentes sistemas sin tener que configurar un DSN para cada sistema. Por ejemplo, puedemos crear un DSN de archivo en una base de datos de informes en el escritorio. Entonces puedemos enviar el archivo a otros usuarios. Esos usuarios pueden guardar el archivo DSN en sus ordenadores y luego apuntar sus aplicaciones de informes al archivo DSN.

   Si desea utilizar un DSN basado en archivo con el controlador ODBC de SQLDB:

Configurar el archivo DSN

  • Vamos a [Fuentes de datos (ODBC)] en las herramientas administrativas del panel de control.
  • Vamos al menú de la pestaña [Archivo DSN], hacemos clic en Agregar, luego seleccionamos <Microsoft Access driver>.
  • A continuación, buscamo la ruta actual del proyecto Lazarus para guardar ahí el archivo .DSN, ya que ese archivo dsn contendrá la configuración necesaria para acceder a su archivo de base de datos (.mdb).
  • Hacemos clic en Siguiente, luego en Finalizar (ahora hemos creado un nuevo archivo .dsn) que se utilizará en la TODBCConnection [FileDSN].

Contenido del archivo DSN

   Como muestra, un archivo DSN que hace referencia una base de datos de MS Access tendrá un aspecto similar a este:

[ODBC]
DRIVER=Microsoft Access Driver (*.mdb)
UID=admin
UserCommitSync=Yes
Threads=3
SafeTransactions=0
PageTimeout=5
MaxScanRows=8
MaxBufferSize=2048
FIL=MS Access
DriverId=25
DefaultDir=
DBQ=YOUR_msaccess.mdb

Configurar el proyecto para usar el DSN basado en archivos

   El uso de rejillas y otros controles vinculados a datos es similar a cualquier otro conector SQLDB, por lo que no se trata aquí.    Como estamos utilizando una conexión ODBC, deberemos tener un objeto TODBCConnection.    Propiedades de TOBDCConnection:

  • FileDSN = la ruta + nombre del archivo DSN guardado anteriormente, P. Ej. C:\milazarus\proyecto1\miArchivo.dsn
  • Nombre de usuario: admin (o el nombre de usuario que necesite si usa la seguridad de Access) este parámetro puede no ser necesario si no se usa seguridad de MS Access.

   No usaremos ninguna otra entrada en las propiedades.


DSN de Sistema/Usuario

   Como se explica en el artículo de ODBC, también es posible usar los DSN de sistema o de usuario, donde los ajustes de conexión se definen en el panel de control de ODBC en lugar de almacenarse en un archivo.    El objeto TODBCConnection tendrá este valor para la propiedad DatabaseName: <name_of_your_DSN>.

Conexión sin DSN

   Como se explica en el artículo de ODBC , también puede crear una conexión sin DSN a su base de datos de Access donde puede especificar todos los parámetros de conexión en código, algo como:

  //Nota: estamos usando el nuevo controlador MS Access, probablemente también podríamos usar el anterior:
  conn.Driver:='Microsoft Access Driver (*.mdb, *.accdb)';
  conn.Params.Add('DBQ=c:\somedirectory\test.mdb');
  ...agregaremos los parámetros que queramos/necesitemos...

    Aquí hay un ejemplo de cómo consultar una tabla en un archivo Access mdb utilizando TODBCConnection, TSQLTransaction y TSQLQuery (el archivo mdb estará en la misma carpeta que el ejecutable):

procedure TForm1.Button1Click(Sender: TObject);
begin
  //connection
  ODBCConnection1.Driver := 'Microsoft Access Driver (*.mdb, *.accdb)';
  ODBCConnection1.Params.Add('DBQ=.\prueba.mdb');      // o especificamos la ruta absoluta al archivo mdb
  ODBCConnection1.Params.Add('Locale Identifier=1031');
  ODBCConnection1.Params.Add('ExtendedAnsiSQL=1');
  ODBCConnection1.Params.Add('CHARSET=ansi');
  ODBCConnection1.Connected := True;
  ODBCConnection1.KeepConnection := True;
     
  //transaction
  SQLTransaction1.DataBase := ODBCConnection1;
  SQLTransaction1.Action := caCommit;
  SQLTransaction1.Active := True;
     
  SQLQuery1.DataBase := ODBCConnection1;
  SQLQuery1.UsePrimaryKeyAsKey := False;
  SQLQuery1.SQL.Text := 'select * from Clientes';
  SQLQuery1.Open;
end;

Código de ejemplo

Lazarus: controles de datos en formularios

   Muestra de código fuente funcional: (Updated2_with_add_delete_update) http://www.mediafire.com/file/ne1jx3zpnwzefq3/msaccesstest2.zip

unit Unit1; 

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, odbcconn, sqldb, db, FileUtil, LResources, Forms, Controls,
  Graphics, Dialogs, DBGrids, DbCtrls, StdCtrls, Printers, PrintersDlgs;

type

  { TForm1 }

  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    Datasource1: TDatasource;
    DBEdit1: TDBEdit;
    DBEdit2: TDBEdit;
    DBGrid1: TDBGrid;
    ODBCConnection1: TODBCConnection;
    PrintDialog1: TPrintDialog;
    SQLQuery1: TSQLQuery;
    SQLTransaction1: TSQLTransaction;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: boolean);
    procedure FormShow(Sender: TObject);
  private
    { private declarations }
    procedure PrintDbGrid(dbGrid:TdbGrid);
  public
    { public declarations }
  end; 

var
  Form1: TForm1; 

implementation

{ TForm1 }

procedure TForm1.FormShow(Sender: TObject);
begin
  //connection
  ODBCConnection1.FileDSN := ExtractFilePath(Application.ExeName) + 'file.dsn';
  ODBCConnection1.Connected := True;
  ODBCConnection1.KeepConnection := True;

  //transaction
  SQLTransaction1.DataBase := ODBCConnection1;
  SQLTransaction1.Action := caCommit;
  SQLTransaction1.Active := True; //not applied to ms access(false)

  SQLQuery1.DataBase := ODBCConnection1;
  SQLQuery1.UsePrimaryKeyAsKey := False;

  SQLQuery1.SQL.Text := 'select * from table1';

  // :id is the parameter for the field name id
  SQLQuery1.deleteSQL.Text := 'delete from table1 where id=:id';
  //:name is the parameter for the field name name
  SQLQuery1.updateSQL.Text := 'update table1 set name=:name where id=:id';

  DataSource1.DataSet := SQLQuery1;
  DBGrid1.DataSource := DataSource1;
  DBGrid1.ReadOnly := true;

  DBEdit1.DataField := 'id';
  DBEdit1.DataSource := DataSource1;
  DBEdit2.DataField := 'name';
  DBEdit2.DataSource := DataSource1;

  SQLQuery1.Open;
end;

function MulDiv(nNumber, nNumerator, nDenominator: Integer): Integer;
begin
  Result:=Round(int64(nNumber)*int64(nNumerator)/nDenominator);
end;
procedure TForm1.PrintDbGrid(dbGrid:TdbGrid);
const
  LeftMargin = 0.05;
  TopMargin = 0.05;
  BottomMargin = 0.05;
var
  i: integer;
  x,y: integer;
begin
  if PrintDialog1.Execute then
    begin
    Printer.BeginDoc;
    Printer.Canvas.Font.Size := 12;

    y := Round(TopMargin*Printer.PageHeight);
    dbGrid.DataSource.DataSet.First;
    while not dbGrid.DataSource.DataSet.Eof do
      begin
      x := Round(LeftMargin*Printer.PageWidth);
      for i := 0 to dbGrid.DataSource.DataSet.FieldCount-1 do
        begin
        printer.Canvas.TextOut(x,y,dbGrid.DataSource.DataSet.Fields[i].AsString);
        x := x + MulDiv(dbGrid.Columns[i].Width,72, dbGrid.Width);
        end;
      dbGrid.DataSource.DataSet.Next;
      y := y + printer.Canvas.TextHeight('A');
      if y > (1-TopMargin-BottomMargin)* Printer.PageHeight then
        begin
        y := Round(TopMargin*Printer.PageHeight);
        Printer.NewPage;
        end;
      end;
    Printer.EndDoc;
    end
    else
    Form1.caption := 'NO PRINTER INSTALLED';
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  PrintDbGrid(DBGrid1);
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
if button2.Caption = 'new' then
begin
  SQLQuery1.Insert;
  button2.Caption := 'save';
  exit
  end
  else
  begin
    if (dbedit1.Text = '') or (dbedit2.Text = '')
    then
    begin
    SQLQuery1.Cancel;
    end
    else
    begin
    if SQLQuery1.State = dsInsert then
       begin
       SQLQuery1.Post;
       SQLQuery1.ApplyUpdates;
       Form1.caption := 'ADDED';
       end;
    end;
  end;
button2.Caption := 'new';
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
if SQLQuery1.RecordCount>0 then
  begin
  SQLQuery1.Delete;
  SQLQuery1.ApplyUpdates;

  //REMOVED or ADD this 2 lines if there is a problem in your SQLdB
  //THESE 2 lines apparently work around a bug in SQLdB for MS_Access dataBase'
  //**** missing reference to bugtracker item; please create a bugtracker item
  //SQLQuery1.Close;
  //SQLQuery1.Open;


  Form1.caption := 'DELETED';
  end;
end;

procedure TForm1.Button4Click(Sender: TObject);
begin
  if SQLQuery1.RecordCount>0 then
  begin
    SQLQuery1.Edit;
    SQLQuery1.Post;
    Sqlquery1.ApplyUpdates;
    Form1.caption := 'UPDATED';
  end;
end;

procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: boolean);
begin
  SQLQuery1.Close;
  CanClose := True;
end;

initialization
  {$I unit1.lrs}

end.

Crear una base de datos 'al vuelo' desde el programa

   Con cualquiera de los controladores ODBC, es posible crear bases de datos de Microsoft Access mediante programación. (Nota: el manejo de errores no se ha probado; actualiza la página si lo probaste)

   Programa de ejemplo:

program CreateAccessDb;

{$mode objfpc}{$H+}

uses
  {$IFDEF UNIX}{$IFDEF UseCThreads}
  cthreads,
  {$ENDIF}{$ENDIF}
  Classes, sysutils,
  LCLType;

Const
   ODBC_ADD_DSN=1;
   ODBC_CONFIG_DSN=2;
   ODBC_REMOVE_DSN=3;
   ODBC_ADD_SYS_DSN=4;
   ODBC_CONFIG_SYS_DSN=5;
   ODBC_REMOVE_SYS_DSN=6;
   ODBC_REMOVE_DEFAULT_DSN=7;
function SQLConfigDataSource(hwndParent: Integer; fRequest: Integer;
  lpszDriverString: PChar; lpszAttributes: PChar): Integer; stdcall; external 'odbccp32.dll';
function SQLInstallerError(iError: integer; pfErrorCode: PInteger; lpszErrorMsg: string; cbErrorMsgMax: integer; pcbErrorMsg: PInteger): integer; stdcall; external 'odbccp32.dll';

function CreateAccessDatabase(DatabaseFile: string): boolean;
var
  DBPChar: PChar;
  Driver: PChar;
  ErrorCode, ResizeErrorMessage: integer;
  ErrorMessage: PChar;
  retCode: integer;
begin
	driver := 'Microsoft Access Driver (*.mdb, *.accdb)';
  { With this driver,
  CREATE_DB/CREATE_DBV12 will create an .accdb format database;
  CREATE_DBV4 will create an mdb
  http://stackoverflow.com/questions/9205633/how-do-i-specify-the-odbc-access-driver-format-when-creating-the-database
  }
  DBPChar:=PChar('CREATE_DBV4="'+DatabaseFile+'"');
  retCode := SQLConfigDataSource(Hwnd(nil), ODBC_ADD_DSN, Driver, DBPChar);
  if retCode<>0 then
  begin
    //try alternate driver
    Driver := 'Microsoft Access Driver (*.mdb)';
    DBPChar:=PChar('CREATE_DB="'+DatabaseFile+'"');
    retCode := SQLConfigDataSource(Hwnd(nil), ODBC_ADD_DSN, Driver, DBPChar);
  end;
  if retCode=0 then
  begin
    result:=true;
  end
  else
  begin
    result:=false;
    ErrorCode:=0;
    ResizeErrorMessage:=0;
    // todo: verify how the DLL is called - use pointers?; has not been tested.
    GetMem(ErrorMessage,512);
    try
      SQLInstallerError(1, @ErrorCode, ErrorMessage, SizeOf(ErrorMessage), @ResizeErrorMessage);
    finally
      FreeMem(ErrorMessage);
    end;
    raise Exception.CreateFmt('Error creating Access database: %s', [ErrorMessage]);
  end;
end;

begin
  writeln('Result: ');
  write(CreateAccessDatabase('d:\cop\t\bla.mdb'));
end.

Problemas con los diferentes conjuntos de caracteres

   Las bases de datos de las veresiones antiguas de Access están codificadas usando un juego de caracteres ANSI (las nuevas probablemente utilicen UNICODE). Por lo tanto, los caracteres que no son ASCII no se muestran correctamente en un DBGrid y otros controles de datos. Esto puede solucionarse adjuntando manejadores a los eventos OnGetText y OnSetText del conjunto de datos después de abrir la BD:

uses
  lconvencoding;
 
procedure TForm1.SQLQuery1AfterOpen(DataSet: TDataSet);
var
  i: Integer;  
begin
  for i:=0 to DataSet.Fields.Count-1 do
  begin
    if DataSet.Fields[i].DataType=ftString then
    begin
      DataSet.Fields[i].OnGetText := @ConvertFromDB;
      DataSet.Fields[i].OnSetText := @ConvertToDB;
    end;
  end;
end;
 
procedure TForm1.ConvertFromDB(Sender: TField; var aText: string; DisplayText: Boolean);
begin
  if not Sender.IsNull then
    aText := WinCPToUTF8(Sender.AsString);  // if encoded in windows default code page
end;
 
procedure TForm1.ConvertToDB(Sender: TField; const aText: string);
begin
  if aText <> '' then 
    Sender.Value := UTF8ToWinCP(aText);
end;

Base de datos de ejemplo

   Microsoft Access se distyribuye con la base de datos de ejemplo Northwind.

   Si únicamente disponemos de Access Runtime, se puede usar esta base de datos alternativa del proyecto Mondial como base de datos de prueba: [1] [1]

Ver también

  • ODBCConn El conector ODBC que este artículo utilizamos