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

Type
  TTeste = class 
  private
  public
    property Id: Integer;
    property Estado: string;
    property Descricao: string;
    property Data: TDateTime;
    property Habitantes: Integer;
    property RendaPerCapta: Currency;
  end;

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:

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;

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:

  Contexto : TRttiContext;
  TipoRtti : TRttiType;
  PropRtti : TRttiProperty;
  Atributo : TCustomAttribute;

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:

type
  TNomeTabela = class(TCustomAttribute)
  private
    FNomeTabela: string;
  public
    constructor Create(ANomeTabela: string);
    property NomeTabela: string read FNomeTabela write FNomeTabela;
  end;

No Create, faça o seguinte:

{ TNomeTabela }

constructor TNomeTabela.Create(ANomeTabela: string);
begin
  FNomeTabela := ANomeTabela;
end;

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

  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;

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:

constructor TCampos.Create(ATipo: TFieldTipo; APk: Boolean);
begin
  FTipo := ATipo;
  FPK   := APk;
end;

function TCampos.IsPk: Boolean;
begin
  Result := False;
end;

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:

function TCampoPk.IsPk: Boolean;
begin
  Result := FPK;
end;

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:

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;

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.

Desenvolvedor de software desde 1995. Em 1998, abriu sua própria empresa, a Lukas Sistemas, desde então passou a atender diversas empresas, principalmente autopeças. Apaixonado por Delphi, porém não o impede de flertar com outras linguagens sempre que possível. Mora na cidade de Balsas/MA com sua esposa e dois filhos.

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 *

Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.