Olá!
Estou de volta para mais um artigo desta série.
Gostaria de lembrar que por enquanto não estou disponibilizando os fontes. Mesmo porque, ainda não temos um código que faça jus o download. OK, confesso: quero que você quebre um pouco a cabeça, forçando a mente a trabalhar, melhorando a assimilação do conteúdo. Mas não se preocupe, em breve começarei a disponibilizar o link com os fontes.
Ajustes no código já trabalhado
Antes de iniciar, quero falar de uma pequena mudança que fiz no código do artigo anterior. Em Atributos.pas, eu alterei a classe TCampos, que antes era assim:
TCampos = class(TCustomAttribute) private FTipo: TFieldTipo; FPK: Boolean; public constructor Create(ATipo: TFieldTipo; APk: Boolean); function IsPk: Boolean; virtual; end;
Agora, está assim:
TCampos = class(TCustomAttribute) public function IsPk: Boolean; virtual; end;
Na function IsPK do TCamposPK:
Resul := True;
Na unit Teste.pas, acima da property ID:
... public [TCampoPk] property Id: Integer read FId write SetId; ...
Excluí o construtor e os campos internos (FTipo e FPK). O Constructor agora não tem mais parâmetros.
A alteração foi feita para simplificar um pouco as coisas e tirar campos que não estavam sendo utilizados. Como estamos no início, pode ser que eu volte atrás, mas por enquanto vamos levar da maneira que está.
Aqui está o código completo da unit Atributo.pas:
unit Atributos; interface uses Rtti; type 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; implementation { 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.
Unidade Base
Temos que ter em mente que quanto mais abstrato for, melhor será o nosso ORM. Visto que, devemos tornar o processo de troca de um conjunto de componentes de acesso por outro o mais transparente possível. Vamos lá!
No Delphi, crie uma nova unit:
E salve com o nome de Base.pas.
O que nós queremos é poder ter acesso ao nosso banco de dados (Firebird) utilizando componentes como o IBX, UIB, DbExpress… sem tantos traumas, não é mesmo? Então, esta unit será a nossa base (é mesmo???) para alcançar este objetivo.
Vamos criar nossa primeira classe em Base.pas:
unit Base; interface uses Classes; type IBaseDados = interface ['{9B0F9364-AB16-4C12-B4B7-4E2287840232}'] end;
Na verdade, é uma interface e tem o objetivo de ser uma interface padrão para todos componentes databases (TUIBDataBase – UIB, TIBDatabase – IBX, etc.). Nela vemos o código GUID (identificador global único [globally unique identifier]) gerado através das teclas Ctrl+Shift+G.
Vamos criar mais uma interface:
ITransacao = interface ['{2F1DCA7A-E7F4-4EC3-BDB2-22B99C8CA7DB}'] end;
Esta interface será utilizada para os componentes “Transactions”, para controle das nossas transações.
Abaixo destas interfaces, vamos inserir um alias:
... type IBaseDados = interface ['{9B0F9364-AB16-4C12-B4B7-4E2287840232}'] end; ITransacao = interface ['{2F1DCA7A-E7F4-4EC3-BDB2-22B99C8CA7DB}'] end; TTabela = class(TObject) end; ...
TTabela será uma classe sem propriedades e campos por nós definidos (pelo menos, não por enquanto). É claro que ela herda as propriedades e métodos de TObject.
Você pode estar se perguntando: já que não tem diferença para o TObject, o que justifica criar este alias?
Bom, o fato de necessitarmos do alias é que ele será utilizado em várias partes do nosso projeto, hora sendo uma classe pai de alguma outra classe hora sendo um parâmetro. Então, queremos restringir o uso destas classes, ou seja, que o parâmetro seja do tipo TTabela e não de outra classe qualquer derivada de TObject. Entendido? Ok!
Abra a unit Teste.pas. Altere a classe TTeste:
unit Teste; interface uses Base, Rtti, Atributos; type [TNomeTabela('Teste')] TTeste = class (TTabela) private FHabitantes: Integer; FDescricao: string; FRendaPerCapta: Currency; FId: Integer; FData: TDateTime; FEstado: string; procedure SetData(const Value: TDateTime); procedure SetDescricao(const Value: string); procedure SetEstado(const Value: string); procedure SetHabitantes(const Value: Integer); procedure SetId(const Value: Integer); procedure SetRendaPerCapta(const Value: Currency); public [TCampoPk] property Id: Integer read FId write SetId; 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; implementation { TTeste } procedure TTeste.SetData(const Value: TDateTime); begin FData := Value; end; procedure TTeste.SetDescricao(const Value: string); begin FDescricao := Value; end; procedure TTeste.SetEstado(const Value: string); begin FEstado := Value; end; procedure TTeste.SetHabitantes(const Value: Integer); begin FHabitantes := Value; end; procedure TTeste.SetId(const Value: Integer); begin FId := Value; end; procedure TTeste.SetRendaPerCapta(const Value: Currency); begin FRendaPerCapta := Value; end; end.
Veja que agora ela descende de TTabela e não mais de TObject (não diretamente). Adicione a unit Base ao uses.
Pronto, agora que nós temos as interfaces para o database e o transaction, vamos criar um DAO abstrato:
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;
Veja que nossa interface declara os métodos básicos de um CRUD (inserção, deleção e salvamento), faltando apenas a recuperação de dados. Implantaremos isso nos próximos artigos da série. Veja também, que temos os métodos de controle das transações:
- InTransaction: irá verificar se existe transação aberta;
- StartTransaction: irá iniciar uma nova transação;
- Commit: irá efetivar as alterações no banco de dados;
- RollBack: irá cancelar a transação aberta.
Abaixo, código completo de 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.
Diagrama atual do nosso projeto:
Chegamos ao fim deste artigo. No próximo, iremos iniciar a criação da classe específica do UIB.
Obrigado e até a próxima!