Finalmente, chegamos aonde a mágica aconte! Ok, exagerei. :)

Neste artigo, iremos iniciar a construção da primeira classe que implementa um componente específico de acesso, o UIB(http://sourceforge.net/projects/uib/).

No Delphi, crie uma nova unidade e salve como DaoUib.pas.

Olhando para o diagrama do artigo anterior, vemos a nossa interface IDaoBase:

Código da unidade Base.pas:

unit Base;

interface

uses Classes;

type
  IBaseDados = interface
  ['{9B0F9364-AB16-4C12-B4B7-4E2287840232}']
  end;

  ITransacao = interface
  ['{2F1DCA7A-E7F4-4EC3-BDB2-22B99C8CA7DB}']
  end;

  TTabela = class(TObject)
  end;

  IDaoBase =  interface
    ['{D06AAE8D-D5F7-47E7-BF11-E26687C11900}']

    function Inserir(ATabela: TTabela): Integer;
    function Salvar(ATabela: TTabela): Integer;
    function Excluir(ATabela: TTabela): Integer;

    function InTransaction: Boolean;
    procedure StartTransaction;
    procedure Commit;
    procedure RollBack;

  end;

implementation

end.

Agora que relembramos o código da base, vamos criar uma classe em nossa nova unidade, DaoUib.pas, chamada TDaoUib:

unit DaoUib;

interface

uses Base, Rtti, Atributos, uib, system.SysUtils;

type
  TDaoUib = class(TInterfacedObject, IDaoBase)
  private
    FDatabase: TUIBDataBase;
    FTransacao: TUIBTransaction;
  public
    Qry: TUIBQuery;

    constructor Create(ADatabase: TUIBDataBase; ATransacao: TUIBTransaction);

    function Inserir(ATabela: TTabela): Integer;
    function Salvar(ATabela: TTabela): Integer;
    function Excluir(ATabela: TTabela): Integer;

    function InTransaction: Boolean;
    procedure StartTransaction;
    procedure Commit;
    procedure RollBack;
  end;

No uses temos, além da Base e SysUtils, a unidade uib para a conexão com o banco de dados e a Rtti já prevendo a utilização deste recurso.

Veja que a classe TDaoUIb implementa a interface IDaoBase. Utilizamos TInterfacedObjetct para termos a nossa contagem por referência que libera a instância automaticamente quando a mesma não existir mais, ou seja, não teremos que nos preocupar com a destruição de objetos que implementem nossa interface.

Mais uma rápida olhada, e vemos também dois campos no private, o FDatabase (para receber a conexão) e o FTransacao (que receberá a transação). E para finalizar, temos uma TUIBQuery, a Qry, que será quem irá executar os comandos SQL. Eu poderia utilizar um TUIBScript, sem problema algum.

Dê CTRL+Shift+C para gerar os métodos.

No constructor Create, temos:

constructor TDaoUib.Create(ADatabase: TUIBDataBase; ATransacao: TUIBTransaction);
begin
  inherited Create;
  if not Assigned(ADatabase) then
    raise Exception.Create('UIBDatabase não informado!');

  if not Assigned(ATransacao) then
    raise Exception.Create('UIBTransaction não informado!');

  FDatabase := ADatabase;
  FTransacao := ATransacao;

  Qry := TUIBQuery.Create(Application);
  Qry.DataBase := FDatabase;
  Qry.Transaction := FTransacao;
end;

function TDaoUib.Inserir(ATabela: TTabela): Integer;
begin
 
end;
 
function TDaoUib.Salvar(ATabela: TTabela): Integer;
begin
 
end;
 
function TDaoUib.Excluir(ATabela: TTabela): Integer;
begin
 
end;

Vamos entender o que o nosso constructor faz:

  • O constructor recebe dois parâmetros, o ADatabase e o ATransacao.
  • Na linha 3, chama o construtor da classe ascendente, que no final das contas será o constructor de TObject. Acredite!
  • Logo em seguida, verifica se foi passado um Database e uma Transacao, através dos parâmetros. Caso não tenha passado um database ou uma transação, uma exceção será gerada.
  • Na linha 10 e 11, os campos internos, FDatabase e FTransacao, recebem os parâmetros ADatabase e ATransacao.
  • Por fim, criamos e configuramos nossa query.

Vamos agora para os métodos responsáveis por nossas transações (InTransaction, StartTransaction, RollBack, Commit):

function TDaoUib.InTransaction: Boolean;
begin
  Result := FTransacao.InTransaction;
end;

procedure TDaoUib.StartTransaction;
begin
  FTransacao.StartTransaction;
end;

procedure TDaoUib.RollBack;
begin
  FTransacao.RollBack;
end;

procedure TDaoUib.Commit;
begin
  FTransacao.Commit;
end;

Neste caso, não há segredo. Vamos em frente… Que o CRUD nos aguarde!

Antes de iniciar a codifição dos métodos de atualização, devemos analisar como iremos efetuar o CRUD. Por exemplo, atente para a declaração do método Excluir:

function TDaoUib.Excluir(ATabela: TTabela): Integer;
begin

end;

Passamos no parâmetro, um objeto do tipo TTabela. Você lembra que a única tabela que criamos até o momento no nosso BD foi a tabela Teste e que criamos uma classe chamada TTeste. Lembra?

Pois bem, será um objeto deste tipo (TTeste) que iremos passar no parâmetro.

A partir daí, com o objeto em “mãos”, iremos utilizar a RTTI para colher seus atributos, métodos e propriedades. Para isso, precisaremos de algumas variáveis dos tipos: TRttiContext, TRttiType, TRttiProperty e TCustomAttribute.

Outro detalhe, nem sempre uma chave primária será composta de apenas um campo. Ela poderá ter 1 ou mais campos. Portanto, o primeiro passo será criar uma função que nos retorne um array contendo os nomes dos campos que fazem parte da chave primária.

Também precisaremos de um método que nos retorne o nome da tabela. Mãos à obra!

Abra a unidade Atributos.pas. E crie o seguinte método:

...
  function PegaNomeTab(AObjeto : TObject) : string;

implementation

function PegaNomeTab(AObjeto : TObject) : string;
var
  Contexto  : TRttiContext;
  TipoRtti  : TRttiType;
  AtribRtti : TCustomAttribute;
begin

  Contexto  := TRttiContext.Create;
  TipoRtti := Contexto.GetType( AObjeto.ClassType );

  for AtribRtti in TipoRtti.GetAttributes do
      if AtribRtti Is TNomeTabela then
        begin
          Result := (AtribRtti as TNomeTabela).NomeTabela;
          Break;
        end;

  Contexto.Free;
end;

O código acima simplesmente faz um loop em todos os atributos em busca do atributo que seja do tipo TNomeTabela. Quando encontra, retorna o conteúdo da propriedade NomeTabela através de um typecast.

Vamos para o método que retorna os campos da chave primária (PK’s):

Declare o método abaixo da declaração do método PegaNomeTab:

...
  function PegaNomeTab(AObjeto : TObject) : string;
  function PegaPks(AObjeto : TObject) : TResultArray;

implementation

...

Detalhe, estamos retornando um array. Então, temos que criar um tipo array. Faça isso, logo abaixo de type:

uses
  Base, Rtti;

type
  TResultArray = array of string;

Para implementar o código da função PegaPks, faremos o seguinte:

function PegaPks(AObjeto : TObject) : TResultArray;
var
  Contexto  : TRttiContext;
  TipoRtti : TRttiType;
  PropRtti : TRttiProperty;
  AtribRtti : TCustomAttribute;
  i: Integer;
begin

  Contexto  := TRttiContext.Create;
  TipoRtti := Contexto.GetType( AObjeto.ClassType );
  i:=0;
  for PropRtti in TipoRtti.GetProperties do
    for AtribRtti in PropRtti.GetAttributes do
      if AtribRtti Is TCampos then
        if (AtribRtti as TCampos).isPk then
        begin
          SetLength(Result, i+1);
          Result[i] := PropRtti.Name;
          inc(i);
        end;

  Contexto.Free;
end;

Abaixo, código completo de Atributos.pas:

unit Atributos;

interface

uses
  Base, Rtti;

type
  TResultArray = array of string;

  TNomeTabela = class(TCustomAttribute)
  private
    FNomeTabela: string;
  public
    constructor Create(ANomeTabela: string);
    property NomeTabela: string read FNomeTabela write FNomeTabela;
  end;

  TCampos = class(TCustomAttribute)
  public
    function IsPk: Boolean; virtual;
  end;

  TCampoPk = class(TCampos)
  public
    function IsPk: Boolean; override;
  end;

  function PegaNomeTab(AObjeto : TObject) : string;
  function PegaPks(AObjeto : TObject) : TResultArray;


implementation

function PegaNomeTab(AObjeto : TObject) : string;
var
  Contexto  : TRttiContext;
  TipoRtti  : TRttiType;
  AtribRtti : TCustomAttribute;
begin

  Contexto  := TRttiContext.Create;
  TipoRtti := Contexto.GetType( AObjeto.ClassType );

  for AtribRtti in TipoRtti.GetAttributes do
      if AtribRtti Is TNomeTabela then
        begin
          Result := (AtribRtti as TNomeTabela).NomeTabela;
          Break;
        end;

  Contexto.Free;
end;

function PegaPks(AObjeto : TObject) : TResultArray;
var
  Contexto  : TRttiContext;
  TipoRtti : TRttiType;
  PropRtti : TRttiProperty;
  AtribRtti : TCustomAttribute;
  i: Integer;
begin

  Contexto  := TRttiContext.Create;
  TipoRtti := Contexto.GetType( AObjeto.ClassType );
  i:=0;
  for PropRtti in TipoRtti.GetProperties do
    for AtribRtti in PropRtti.GetAttributes do
      if AtribRtti Is TCampos then
        if (AtribRtti as TCampos).isPk then
        begin
          SetLength(Result, i+1);
          Result[i] := PropRtti.Name;
          inc(i);
        end;

  Contexto.Free;

end;

{ TCampos }

function TCampos.IsPk: Boolean;
begin
  Result := False;
end;

{ TCampoPk }

function TCampoPk.IsPk: Boolean;
begin
  Result := True;
end;

{ TNomeTabela }

constructor TNomeTabela.Create(ANomeTabela: string);
begin
  FNomeTabela := ANomeTabela;
end;

end.

Agora, um teste básico para ver se está funcionando. Crie um novo form e salve como ufrmTesteAtributos.pas:

E deixe ele parecido com este:

Adicione ao uses deste formulário as unidades Atributos e Teste. No clique do primeiro botão, coloque:

procedure TForm2.Button1Click(Sender: TObject);
var
  ATab: TTeste;
begin
  ATab := TTeste.Create;
  try
    Memo1.Clear;
    Memo1.Lines.Add(PegaNomeTab(ATab));
  finally
    ATab.Free;
  end;
end;

No segundo botão, coloque:

procedure TForm2.Button2Click(Sender: TObject);
var
  ATab: TTeste;
  Pk: TResultArray;
  chave: string;
begin
  ATab := TTeste.Create;
  try
    Pk := PegaPks(ATab);
     Memo1.Clear;
     for chave in Pk do
       Memo1.Lines.Add(chave);
  finally
    ATab.Free;
  end;
end;

Deixe apenas este formulário na relação de Auto-create forms:

Assim, quando executamos a aplicação, o form frmTesteAtributos será aberto. Dê um clique nos botões para obter o resultado abaixo:


E se por exemplo uma tabela tivesse 2 campos na chave primária. Vamos simular isso fazendo uma pequena alteração na classe Teste:

  public
    [TCampoPk]
    property Id: Integer read FId write SetId;
    [TCampoPk]  // <-- inserimos aqui, mais um atributo para definir o Estado como sendo chave primária...
    property Estado: string read FEstado write SetEstado;
    property Descricao: string read FDescricao write SetDescricao;
    property Data: TDateTime read FData write SetData;
    property Habitantes: Integer read FHabitantes write SetHabitantes;
    property RendaPerCapta: Currency read FRendaPerCapta write SetRendaPerCapta;
  end;

Compile e execute novamente a aplicação. Faça o teste. O resultado é visto abaixo:

Funciona que é uma beleza! Agora, lembre-se de tirar o atributo TCampoPK do campo Estado, visto que ele não faz parte da chave primária.

No próximo artigo, iremos implementar os métodos excluir, inserir e salvar de TDaoUIB.

Fico por aqui, obrigado e até a próxima!

Desenvolvedor de software desde 1995. Em 1998, abriu sua própria empresa, a Lukas Sistemas, desde então passou a atender diversas empresas, principalmente autopeças. Apaixonado por Delphi, porém não o impede de flertar com outras linguagens sempre que possível. Mora na cidade de Balsas/MA com sua esposa e dois filhos.

2 thoughts on “Classe TDaoUIB – Que tal um ORM Básico? Parte 4”

  1. Prezado.

    Excelente material.
    Na unit DaoUib.pas você utiliza tipos
    FDatabase: TUIBDataBase; FTransacao: TUIBTransaction;

    Eu hoje estou utilizando o XE6 e defini um DataModule com dbExpress. Neste caso uso componentes TSQLConnection, TSQLDataset, TDataSetProvider e TClientDataSet.

    No caso de implementar essa DaoUib, eu teria que modificar os tipos privados para se adequar ao DBExpress, ou viajei na maionese ?

  2. Prezado.

    Excelente material.
    Na unit DaoUib.pas .

    Eu hoje estou utilizando o XE7 com FireDac usando banco de dados SQL Server 2008.

    No caso de implementar essa DaoUib,o que eu teria que modificar para funcionar com o FireDac ?

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.