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!
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 ?
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 ?