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:

[sourcecode language=”delphi”]
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.
[/sourcecode]

Agora que relembramos o código da base, vamos criar uma classe em nossa nova unidade, DaoUib.pas, chamada TDaoUib:
[sourcecode language=”delphi”]
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;
[/sourcecode]

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:
[sourcecode language=”delphi”]
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;
[/sourcecode]

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):
[sourcecode language=”delphi”]
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;
[/sourcecode]

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:
[sourcecode language=”delphi”]
function TDaoUib.Excluir(ATabela: TTabela): Integer;
begin

end;
[/sourcecode]

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:
[sourcecode language=”delphi”]

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;
[/sourcecode]

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:
[sourcecode language=”delphi”]

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

implementation


[/sourcecode]

Detalhe, estamos retornando um array. Então, temos que criar um tipo array. Faça isso, logo abaixo de type:
[sourcecode language=”delphi”]
uses
Base, Rtti;

type
TResultArray = array of string;
[/sourcecode]

Para implementar o código da função PegaPks, faremos o seguinte:
[sourcecode language=”delphi”]
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;
[/sourcecode]

Abaixo, código completo de Atributos.pas:
[sourcecode language=”delphi”]
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.
[/sourcecode]

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:
[sourcecode language=”delphi”]
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;
[/sourcecode]

No segundo botão, coloque:
[sourcecode language=”delphi”]
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;
[/sourcecode]

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:
[sourcecode language=”delphi”]
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;
[/sourcecode]

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!

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 *