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