Como criar um aplicação com provider independente

Você precisa estar logado para dar um feedback. Clique aqui para efetuar o login
Para efetuar o download você precisa estar logado. Clique aqui para efetuar o login
Confirmar voto
0
 (6)  (0)

Esse artigo serve para mostrar como criar uma aplicação Win32 que usa banco de dados ORACLE, MSSQL, qualquer coisa que seja independente do provider usado: dbexpress, bde, ibo, etc...

Ola Pessoal,
 
Hoje já faz mais de 11 anos que desenvolvo sistemas comerciais. E algo que tenho percebido em 100% das aplicações (ao menos nas que vi) é que sempre, sempre, nos amarramos a alguma tecnologia do momento. Por exemplo: Quem vem acompanhando o delphi desde a suas primeiras versões, teve o desprazer (assim como eu) de usar TTable com Paradox (Égua! Nunca mais).
 
Voltando ao assunto. Daí surgiram outras novidades! Mais componentes de acesso a dados, controles dataware para simplificar a nossa mão de obra, datamodules e a maldição de jogar os campos no FieldsEditor dos datasets (outra porcaria que por sinal muita gente ainda vem fazendo).  Ai! O sonhado BDE começou a dar pau de todo jeito. Ninguém tinha mais sossego na vida. Tinha que adivinhar qual versão do BDE era compatível com Oracle, MSSQL, Firebird, Access, etc...
 
Com o DBExpress, ADO e Componentes de acesso nativo! Todo mundo queria sair do BDE! E então, novos erros foram encontrados. Isso mesmo! A liberdade custa caro meus amigos. Eis um novo problema! Como sair do BDE se em todo burado tenho TTable, TQuery enfiado no meu sistema? Não teve outro jeito, senão sair trocando tudo no braço. Pois é! Hoje temos novos componentes de acesso a dados que vem com a ferramenta delphi e ainda temos componentes de terceiros FREE e PAGOS que prometem resolver nossos problemas.
 
Então vem a pergunta! Qdo isso começar a dar problema de novo? Como vou migrar para algo melhor? Vou ter que sair trocando tudo de novo? A resposta é não. Eis uma dica que passei a usar que tem resolvido 99.99% dos meus problemas. Observando o ambiente .NET, tive a feclidade de INVEJAR as Interfaces de Acesso a Dados IDBConnection, IDataReader, etc...
 
Pensando nisso! Resolvi criar algo mais simples, mas que resolvesse meu problema. O que foi feito então? Criei uma camada simples de acesso a dados. E agora não preciso mais me preocupar se estou usando DBExpress, ADO, BDE, etc... Se um desses providers falhar comigo troco para outro em questão de minutos. Não mais que 2 minutos. Olha só o que foi feito.
 
1. Criando um novo objeto para executar comandos SQL:
 
TPLSQL = class(TComponent)
 
Essa ai vai ser nossa classe que irá acessar os dados do nosso BD. Começarei explicando a sua principal propriedade. A que me diz qual Connection estou usando: BDE, ADO, etc...
 
uses DB; <- não esqueçam disso.
 
TPLSQL = class(TComponent)
private
  FConn: TCustomConnection;
protected
  function get_Conn: TCustomConnection; virtual;
  procedure set_Conn(const Value: TCustomConnection); virtual;
public
  property Conn: TCustomConnection read get_Conn write set_Conn;
end;
 
O que é TCustomConnection? Essa é uma classe base dos nossos componentes de acesso a dados como por exemplo: TDataBase, TSQLConnection, TADOConnection e TIBDataBase. Tendo ela como propriedade poderei conectar qualquer um desses. Por Exemplo:
 
var
  obj : TPLSQL;
begin
  obj := TPLSQL.Create(Self); 
  obj.Conn := DataBase1; // se eu estiver usando BDE
 
  ou ainda
 
  obj.Conn := SQLConnection1; // se eu estiver usando TSQLConnection
 
  etc....
 
2. Criando uma propriedade para nossos comandos SQL:
 
TPLSQL = class(TComponent)
private
  FConn: TCustomConnection;
  FSQL: TStrings;
protected
  function get_Conn: TCustomConnection; virtual;
  procedure set_Conn(const Value: TCustomConnection); virtual;
 
  function get_SQL: TStrings; virtual;
  procedure set_SQL(const Value: TStrings); virtual;
public
  property Conn: TCustomConnection read get_Conn write set_Conn;
  property SQL: TStrings read get_SQL write set_SQL;
 
  constructor Create(AQwner: TComponent); override;
  destructor Destroy; override;
end;
 
implementation
 
constructor TPLSQL.Create(AQwner: TComponent);
begin
  inherited;
  FSQL := TStringList.Create; // inicializa
end;
destructor TPLSQL.Destroy;
begin
  FSQL.Free; // libera o objeto
  inherited;
end;
function TPLSQL.get_SQL: TStrings;
begin
  result := FSQL; // metodo de leitura da propriedade SQL
end;
 
procedure TPLSQL.set_SQL(const Value: TStrings);
begin
  FSQL.Assign(Value); // metodo de escrita da propriedade
end;
 
3. Descobrindo o Componente de acesso a dados associado a propriedade Conn:
 
Agora temos um problema! Qual componente foi usado? BDE? ADO? Iremos resolver isso com a implementação desses métodos simples.
 
uses SqlExpr, ADODB, DB;
 
function isBDE(Conn: TCustomConnection): boolean;
begin
  result := Conn is TDatabase;
end;
function isDBExpress(Conn: TCustomConnection): boolean;
begin
  result := Conn is TSQLConnection;
end;
function isADO(Conn: TCustomConnection): boolean;
begin
  result := Conn is TADOConnection;
end;

 
4. Executando comandos SQL que não geram Cursor:
 
Criando nossa exceção padrão. Iremos processar a exceção abaixo em caso de ser usado um Componente para acesso a dados diferente do suportado pela nossa classe. Ou seja, diferente de Ado, BDE e DBExpress.
 
EDriverSuppor = class(Exception)
public
  constructor Create; virtual;
end;
 
implementation
constructor EDriverSuppor.Create;
begin
  inherited Create('Driver de conexão não suportado!');
end;
 
O próximo passo é criar um novo método, genérico, para executar comandos SQL do tipo INSERT, UPDATE, DELETE, Create Table, Alter Table e etc... Neste caso criaremos uma função que retornará uma Query de acordo com a classe associada a propriedade
Conn do nosso objeto.
 
TPLSQL = class(TComponent)
private
  FConn: TCustomConnection;
  FSQL: TStrings;
protected
  function get_Conn: TCustomConnection; virtual;
  procedure set_Conn(const Value: TCustomConnection); virtual;
 
  function get_SQL: TStrings; virtual;
  procedure set_SQL(const Value: TStrings); virtual;
 
  // cria um objeto query de acordo com o componente associado a Conn
  function CreateDataSetQuery: TDataSet; virtual;
public
  property Conn: TCustomConnection read get_Conn write set_Conn;
  property SQL: TStrings read get_SQL write set_SQL;
 
  constructor Create(AQwner: TComponent); override;
  destructor Destroy; override;
 
  // essa função processa um comando SQL e retorna o número de  linhas afetadas
  function ExecuteDirect: integer; virtual;
end;
 
implementation
 
function TPLSQL.CreateDataSetQuery: TDataSet;
begin
  // se for BDE cira TQuery
  if isBDE(Conn) then
    result := TQuery.Create(owner)
  // se for DBExpress cira TSQLQuery
  else if isDBExpress(Conn) then
    result := TSQLQuery.Create(owner)
  // se for ADO cria TADOQuery
  else if isADO(COnn) then
    result := TADOQuery.Create(Owner)
  else
    result := nil;
end;
function TPLSQL.ExecuteDirect: integer;
var
  loQry: TDataSet;
begin
  try
    loQry  := CreateDataSetQuery; // implementado logo acima
    result := -1;
    try
      // se nao eh um componente suportado
      if not Assigned(loQry) then
        raise EDriverSuppor.Create;
 
      // executa como bde
      if isBDE(Conn) then
        begin
        TQuery(loQry).DatabaseName := TDataBase(Conn).DatabaseName;
        TQuery(loQry).SQL.Assign(SQL);
        TQuery(loQry).ExecSQL;
        result := TQuery(loQry).RowsAffected;
        end
      // executa como ADO
      else if isADO(Conn) then
        begin
        TAdoQuery(loQry).Connection := TADOConnection(Conn);
        TAdoQuery(loQry).SQL.Assign(SQL);
        result := TAdoQuery(loQry).ExecSQL;
        end
      // executa como DBExpress
      else
        begin
        TSQLQuery(loQry).SQLConnection := TSQLConnection(Conn);
        TSQLQuery(loQry).SQL.Assign(SQL);
        result := TSQLQuery(loQry).ExecSQL;
        end;
    finally
      if Assigned(loQry) then
        loQry.Free;
    end;
  except
    on e: exception do
      raise;
  end;
end;
 
5. Executando comandos SQL que GERAM Cursor:
 
No passo anterior criamos um método para comandos INSERT, UPDATE e etc. Agora iremos criar um comando que além de processar o script SQL ele irá nos retornar os dados processados por esse comando. Para isso! Iremos precisar criar um método que leia um DataSet genérico e o transforme num OleVariant. Pois esse tipo de dado
pode ser aberto dentro de um ClientDataSet. Vejamos como proceder.
 
uses Provider;
 
function getDataSet(Ds: TDataSet): OleVariant;
var
  provider: TDataSetProvider;
begin
  provider := TDataSetProvider.Create(nil);
  try
    provider.DataSet := Ds;
    Result := provider.Data;
  finally
    provider.Free;
  end;
end;
 
Agora vamos criar nosso último método da Classe TPLSQL.
 
TPLSQL = class(TComponent)
private
  FConn: TCustomConnection;
  FSQL: TStrings;
protected
  function get_Conn: TCustomConnection; virtual;
  procedure set_Conn(const Value: TCustomConnection); virtual;
 
  function get_SQL: TStrings; virtual;
  procedure set_SQL(const Value: TStrings); virtual;
 
  // cria um objeto query de acordo com o componente associado a Conn
  function CreateDataSetQuery: TDataSet; virtual;
public
  property Conn: TCustomConnection read get_Conn write set_Conn;
  property SQL: TStrings read get_SQL write set_SQL;
 
  constructor Create(AQwner: TComponent); override;
  destructor Destroy; override;
 
  // essa função processa um comando SQL e retorna o número de  linhas afetadas
  function ExecuteDirect: integer; virtual;
 
  // essa função processa um comando SQL e retorna os registros do mesmo
  function ExecuteReader: OleVariant; virtual;
end;
 
implementation
 
function TPLSQL.ExecuteReader: OleVariant;
var
  loQry: TDataSet;
begin
  try
    loQry  := CreateDataSetQuery;
    result := null;
    try
      // se eh um componente suportado
      if not Assigned(loQry) then
        raise EDriverSuppor.Create;
 
      // executa como bde
      if isBDE(Conn) then
        begin
        TQuery(loQry).DatabaseName := TDataBase(Conn).DatabaseName;
        TQuery(loQry).SQL.Assign(SQL);
        end

      // executa como ado
      else if isADO(Conn) then
        begin
        TAdoQuery(loQry).Connection := TADOConnection(Conn);
        TAdoQuery(loQry).SQL.Assign(SQL);
        end

      // executa como dbexpress
      else
        begin
        TSQLQuery(loQry).SQLConnection := TSQLConnection(Conn);
        TSQLQuery(loQry).SQL.Assign(SQL);
        end;
 
      result := getDataSet(loQry);
    finally
      if Assigned(loQry) then
        loQry.Free;
    end;
  except
    on e: exception do
      raise;
  end;
end;
 
6. Vejam alguns exemplos de como usar essa classe
 
1. Exemplo:
 
var
  loPL: TPLSQL;
begin
  loPL:= TPLSQL.Create(Self);
  loPL.Conn := Database1; // bde
  loPL.SQL.Add('DELETE FROM CLIENTES');
  loPL.ExecuteDirect;
  loPL.Free;
end;
No exemplo acima, se vc não quiser mais usar BDE no seu sistema basta trocar a linha
 
  loPL.Conn := Database1; // bde
 
Por:
 
  loPL.Conn := SQLConnection1; // dbexpress
Agora vejamos como retornar dados para um DBGrid
 
2. Exemplo:
 
var
  loPL: TPLSQL;
begin
  loPL:= TPLSQL.Create(Self);
  loPL.Conn := ADOConnection1; // ADO
  loPL.SQL.Add('SELECT * FROM CLIENTES');
 
  ClienteDataSet.Close;
  ClienteDataSet.Data := loPL.ExecuteReader;
  ClienteDataSet.Open;
 
  loPL.Free;
end;
 
Os fontes para download estão disponíveis.
Eles estão em anexo ao artigo.
É só clicar em Download!
 
 
Obrigado a todos!
E espero que possam melhorar a classe e divulgá-la.
 
 
Você precisa estar logado para dar um feedback. Clique aqui para efetuar o login
Receba nossas novidades
Ficou com alguma dúvida?