Na primeira parte, criamos o banco de dados e conhecemos a estrutura da primeira tabela do nosso projeto:

[sourcecode language=”delphi”]
Type
TTeste = class
private
public
property Id: Integer;
property Estado: string;
property Descricao: string;
property Data: TDateTime;
property Habitantes: Integer;
property RendaPerCapta: Currency;
end;
[/sourcecode]

No Delphi, crie uma nova unit e salve com o nome de Teste.pas. Abaixo de interface, coloque o código acima e depois dê Ctrl+Shift+C para gerar os getters e setters:

[sourcecode language=”delphi”]
unit Teste;

interface

type
TTeste = class
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
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;
[/sourcecode]

Temos, enfim, a classe que representa a tabela Teste em nosso banco de dados.

Continuando, iremos buscar informações desta classe, como o nome das propriedades (campos), tipos de cada campo (string, integer, datetime, currency…). Este processo é conhecido como Reflection:

Em ciência da computação, reflexão computacional (ou somente reflexão) é a capacidade de um programa observar ou até mesmo modificar sua estrutura ou comportamento. Tipicamente, o termo se refere à reflexão dinâmica (em tempo de execução), embora muitas linguagens suportem reflexão estática(em tempo de compilação). A reflexão é mais comum em linguagens alto nível, como SmallTalk e menos comum em linguagens de mais baixo nível como o C.
Geralmente, reflexão é a atividade numa computação que permite que um objeto obtenha informações sobre sua estrutura e sobre os motivos de sua computação. O paradigma de programação por trás dessa capacidade é chamado programação reflexiva.
Quando o código fonte de um programa é compilado, a informação sobre sua estrutura normalmente se perde quando o código de baixo nível (tipicamente em assembly) é produzido. Já num sistema que suporta reflexão, essa informação é preservada como metadados, anexados ao código gerado.
(Wikipedia)

Utilizaremos a nova RTTI do Delphi, que nos trouxe maior profundidade aliada a uma maior facilidade. Para maior entendimento, sugiro que dê uma boa lida no artigo Rodrigo Leonhardt.

Bom, pelo artigo fica claro a facilidade na obtenção de informações oriundas de nossas classes. Não significa que antes isso era impossível. Claro que era possível, como pode ser visto num post quando eu ainda utilizava o Delphi 7, porém, não era tão simples quanto é atualmente.

Quando for necessário buscar alguma informação da classe, definiremos basicamente as variáveis abaixo:

[sourcecode language=”delphi”]
Contexto : TRttiContext;
TipoRtti : TRttiType;
PropRtti : TRttiProperty;
Atributo : TCustomAttribute;
[/sourcecode]

E utilizaremos alguns métodos:

  • GetType – do contexto, onde obtemos um objeto RTTI com a descrição do tipo de dado
  • GetProperties – retorna as propriedades existentes no RTTIType retornado
  • GetAttributes – retorna os atributos

Veremos o funcionamento em detalhe nos próximos artigos.

Mas aí, olhando para a classe Teste, você pode estar se perguntando:

Ok, consigo informações sobre métodos, propriedades, campos internos, etc… Mas além disso, preciso saber quais os campos que farão parte da chave primária e também o nome propriamente dito da tabela. Como proceder?

Atributos

Iremos lançar mão de um novo recurso do Delphi, o chamado Atributo:

Um recurso muito interessante que foi viabilizado pela nova arquitetura da RTTI é o uso de atributos. Basicamente, um atributo permite que você defina informações adicionais sobre um método, propriedade ou classe que vão além das pré-existentes.

O primeiro passo para utilizar um atributo é implementar uma classe descendente de TCustomAttribute que armazene as informações desejadas.(Rodrigo Leonhardt)

A princípio, precisaremos de um atributo que informe campos que fazem parte da chave primária (PK) e um atributo que nos diga o nome da Tabela.

Crie uma nova unit chamada Atributos.pas e nela crie uma nova classe chamada TNomeTabela:

[sourcecode language=”delphi”]
type
TNomeTabela = class(TCustomAttribute)
private
FNomeTabela: string;
public
constructor Create(ANomeTabela: string);
property NomeTabela: string read FNomeTabela write FNomeTabela;
end;
[/sourcecode]

No Create, faça o seguinte:

[sourcecode language=”delphi”]
{ TNomeTabela }

constructor TNomeTabela.Create(ANomeTabela: string);
begin
FNomeTabela := ANomeTabela;
end;
[/sourcecode]

Criamos o atributo responsável pelo nome da nossa tabela. Agora, criaremos o atributo para as PK’s:

[sourcecode language=”delphi”]
TCampos = class(TCustomAttribute)
private
FTipo: TFieldTipo;
FPK: Boolean;
public
constructor Create(ATipo: TFieldTipo; APk: Boolean);
function IsPk: Boolean; virtual;
end;

TCampoPk = class(TCampos)
public
function IsPk: Boolean; override;
end;
[/sourcecode]

Teremos uma classe base para os campos (TCampos) e uma classe filha para um campo PK (TCampoPk). Na classe base, temos a implementação dos métodos Create e IsPk da seguinte forma:

[sourcecode language=”delphi”]
constructor TCampos.Create(ATipo: TFieldTipo; APk: Boolean);
begin
FTipo := ATipo;
FPK := APk;
end;

function TCampos.IsPk: Boolean;
begin
Result := False;
end;
[/sourcecode]

No momento, talvez fique um pouco sem sentido a criação de uma classe base. Porém, eu já estou deixando em aberto uma possível ampliação do nosso código (quem sabe num futuro próximo não teremos outros tipos de campos?).

Para a classe filha, temos o seguinte:

[sourcecode language=”delphi”]
function TCampoPk.IsPk: Boolean;
begin
Result := FPK;
end;
[/sourcecode]

Neste caso, quando criarmos o atributo, marcaremos o FPK como True, e o retorno do IsPk será verdadeiro. Poderíamos já definir o Result como sendo True diretamente, não necessitando do campo interno FPK. Eu vou deixar como está, se necessário, depois voltamos aqui e alteramos.

Feito isso, voltemos para a unit Teste.pas. Vamos inserir os atributos:

[sourcecode language=”delphi”]
unit Teste;

interface

uses Rtti, Atributos; // <– inserimos em uses as unidades necessárias.

type
[TNomeTabela(‘Teste’)] // <– atributo para o nome da tabela
TTeste = class
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(ftInteiro, True)] // <– atributo que define o campo ID como PK
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;
[/sourcecode]

Inserimos os atributos acima do item desejado. Para o atributo do nome da tabela inserimos acima do nome da classe. E para a chave primária, o atributo foi inserido acima do nome da propriedade Id, que é a chave primária na tabela Teste.

Veja, a nossa classe já tem tudo o que precisamos para fazer o CRUD. Temos o nome da tabela, a chave primária, os campos e os tipos. Para um ORM básico, praticamente não teremos alteração na classe. Manteremos o sistema simples e de fácil manutenção. Por exemplo, quando surgir um novo campo, não precisaremos nos preocupar com comandos Sql. Bastará inserir a nova propriedade na classe e automaticamente tudo estará pronto para a persistência na base de dados.

Vou ficando por aqui. No próximo artigo, vamos iniciar a construção das classes básicas, que nos permitirão flexibilizar nossa aplicação. Obrigado e até a próxima.

8 thoughts on “Reflection – Que tal um ORM Básico? Parte 2”

  1. Muito bom o seu blog, sou novo em desenvolvimento utilizando delphi e estou aprendendo bastante com seus artigos, todos são bastante claros e objetivos.

    Porem fiquei com uma dúvida, como faria para implementar o mapeamento de tabelas tipo master/details?

    Como faria para identificar quais campos são chaves estrangeiras nas tabelas, seria necessário criar um objeto do tipo referente a tabela que é chave estrangeira na classe da referente as tabelas details ?

    Obrigado.

  2. Mto bom o seu blog, principalmente para iniciantes pela sua abordagem simples e direta, vc teria algum exemplo de classes que tenham outras classe.
    tendereco=class…

    tcliente=class
    id:integer
    endereco:TEndereco…

    de como fazer para pegar o tendereco via rrti par fazer o insert e update….
    Desde ja agradeço

Deixe um comentário

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