Você está na página 1de 58
2004 - http://www.liws.com.br/depo

2004 - http://www.liws.com.br/depo

I

I DePO - Delphi Persistent Objects

DePO - Delphi Persistent Objects

I DePO - Delphi Persistent Objects

Índice

Introdução

1 Introdução 2 Apresentação 3 Sobre o DePO 4 Instalação 5 "TODO List" e "Problemas
1
Introdução
2
Apresentação
3
Sobre o DePO
4
Instalação
5
"TODO List" e "Problemas conhecidos"
6
Material de apoio

Arquitetura

0

4
4

4

4

4

5

8

8

10
10
1
1

Como o DePO funciona

 

10

2
2

Classes e Propriedades

10

3
3
Units
Units

12

4
4

Declarando Objetos

 

12

5
5

Mapeamento de Objetos

 

13

 

De Objetos para RDB

13

Mapeando Objetos

 

15

6
6

Sincronizando o banco de dados

17

Trabalhando com Objetos

CRUD Criando Recuperando Alterando Excluindo Operações em Cascata
CRUD
Criando
Recuperando
Alterando
Excluindo
Operações em Cascata

Problemas com Operações em Cascata

Listas (Coleções) OID Generalização
Listas (Coleções)
OID
Generalização

Desenvolvendo Aplicações

20
20

20

20

20

21

21

21

22

24

26

27

31
31
1
1

Relacionamento entre os Objetos

31

2
2

(Re)Utilizando os Objetos

 

33

Criando Interfaces para Usuários

36
36
1
1

Interfaces RAD

36

Conteúdo

II

Técnicas Avançadas

38
38
1
1
Tipos de dados Imagem, Memo Boolean TDateTime
Tipos de dados
Imagem, Memo
Boolean
TDateTime
 

38

38

 

41

42

2
2

Clonando Objetos

 

42

3
3

Utilizando Proxy

 

43

4
4

Critérios para recuperar Objetos

44

5
5

Ordem de recuperação (Sort)

 

46

6
6

Transações

 

46

Extras
Extras
49
49
1
1
   

49

História

55
55
Introdução I
Introdução
I

Introdução

4

1
1

Introdução

1.1
1.1

Introdução

 

Attention The job started by Liws aim to work together with brazilian developer, but if you don't know portuguese, contact us, we will publish english documentation if someone need.

Atenção O trabalho iniciado pela Liws tem como objetivo atender a comunidade de desenvolvedores brasileiros, mas se você não conhece português, entre em contato com a Liws, nós estaremos disponibilizando materias em Inglês se alguém precisar.

Nota: Esta documento é preliminar e está sujeito a mudanças.

Esta versão esta disponível para que você possa conhecer o funcionamento do DePO e para que possa contribuir para o desenvolvimento do mesmo.

Se você desejar contribuir com o manual, programas exemplos ou com a melhora do código do DePO entre em contato pelo email depo@liws.com.br.

Um blog está sendo escrito sobre o desenvolvimento do DePO em http://www.liws.com.br/depo até que uma página oficial seja publicada.

1.2
1.2

Apresentação

 

"A good business model provides a software-independent description of the business processes to be automated, thereby promoting a good understanding of priorities and risks prior to technology selection."

Introduction to Business Modeling Using the Unified Modeling Language (UML)

Conforme este documento da Rational diz, "Um bom modelo de negócios apresenta uma descrição dos processos de negócio, indentente do software, porém promovendo um bom entendimento das prioridades e riscos antes da seleção da tecnologia".

Apesar desta afirmação abstrair a tecnologia, se você esta lendo este manual, provavelmente está pensando em utilizar o DePO e o Delphi, assim sendo, vamos a outra visão desta afirmação, um bom modelo, deve ser independente de como será armazenado e independente de como será apresentado ao usuário final, isto é o que o DePO pretende fazer, disponibilizar uma base para se criar o modelo de negócios, encapsulando todo o comportamento necessário para armazenar e recuperar os dados de fontes de dados diversas.

[Uma imagem apresentando a arquitetura DePO, algo como a que está na documentação da Arquitetura do Object Space, precisamos de artistas aqui]

1.3
1.3

Sobre o DePO

 

Delphi Persistent Objects encapsula todo o comportamento necessário para se comunicar com fontes de dados. DePO utiliza um esquema de mapeamento para determinar que campos e tabelas da fonte

5

5 DePO - Delphi Persistent Objects

DePO - Delphi Persistent Objects

5 DePO - Delphi Persistent Objects

de dados serão mapeados para as propriedades dos objetos. O que significa que campos e tabelas serão utilizados para persistir dados dos objetos, e serão utilizados para popular as propriedades dos objetos recuperados das fontes de dados.

DePO foi implementado com base no modelo apresentado por Scott Ambler no documento "The Design of a Robust Persistent Layer for relational Databases" disponível junto com outros documentos relacionados em http://www.ambysoft.com.

[Incluir a data que o projeto começou] A versão inicial foi implementada por Cosimo de Michele e está disponível em http://sourceforge.net/projects/depo, a versão disponível em http://www.liws.com.br/depo difere da disponível no Source Forge, por algumas correções enviadas pelo Cosimo e algumas alterações implementadas pelo Liws e seus colaboradores, aconselho que você utilize a vesão disponível pela Liws.

No momento em que este manual esta sendo escrito (Julho de 2004) a Microsoft está trabalhando no desenvolvimento de uma camada de persistência chamada ObjectSpace que pelo que sei se parece muito com a implementação do DePO e tem suas bases no modelo proposto por Scott Ambler, uma leitura na documentação do ObjectSpace pode ajudar a entender os conceitos de uma camada de persistência, http://longhorn.msdn.microsoft.com/lhsdk/ndp/daconoverviewofobjectspacesarchitecture.aspx.

1.4
1.4

Instalação

Baixe a última versão do DePO de http://www.liws.com.br/depo/arquivos.

Versões do Delphi suportadas:

Esta versão foi testada no Delphi 7, provavelmente você consiga instalar em uma versão anterior do delphi com poucas alterações, devido as mudanças das uses no delphi. Provavelmente deve funcionar perfeitamente no Kylix 3, você deve apenas remover a unit dpoADOMechanism.pas.

Atenção:

Se você tiver uma versão anterior do DePO remova-a do Delphi e apague os arquivos Depo.bpl e Depo.dcp do diretório de Bpl´s, se você usa o Delphi 7 provavelmente ele estará em "C:\Arquivos de

programas\Borland\Delphi7\Projects\Bpl"

Instalando:

1) Decompacte o arquivo deposources.zip Serão criados os diretórios:

Source

Lib

Doc

2) Abra o pacote Depo.dpk no Delphi Em Options|Diretories/Condicionals define o diretório de Output das Units para o diretório Lib

Introdução

6

Introdução 6 3) Clique em Compile 4) Clique em Install © <2004> http://www.liws.com.br/depo

3) Clique em Compile

Introdução 6 3) Clique em Compile 4) Clique em Install © <2004> http://www.liws.com.br/depo

4) Clique em Install

7

7 DePO - Delphi Persistent Objects

DePO - Delphi Persistent Objects

7 DePO - Delphi Persistent Objects
7 DePO - Delphi Persistent Objects 5) Os componentes estarão na paleta DePO 6) Para compilar

5) Os componentes estarão na paleta DePO 6) Para compilar projetos com DePO inclua o diretório Lib no menu Tools|Environment Options|Library|Library Path

Lib no menu Tools|Environment Options|Library|Library Path O DePO está pronto para ser usado, aproveite :) ©

O DePO está pronto para ser usado, aproveite :)

Introdução

8

1.5
1.5

"TODO List" e "Problemas conhecidos"

 

Implementar critérios não implementados no DePO Terminar a documentação Escrever novos mecanismos de persistência (XML, Zeos, IBX por exemplo) Criar um site para o DePO

Criar mais exemplos

- Relatórios

- Web

- Outros bancos de dados

- com TdpoDataSet

Script para criar o banco de dados

 

- Criar ForeignKey

- Campos em sincronismo (Blob, Boolean)

 

- PrimaryKey em Sincronismo

1.6
1.6

Material de apoio

 

Fontes de informações úteis Fórum OODesign http://www.oodesign.com.br AmbySoft - Scotty Ambler http://www.ambysoft.com Newsgroup Borland newsgroups.borland.com grupo borland.public.delphi.oodesign Microsoft ObjectSpace http://longhorn.msdn.microsoft.com/lhsdk/ndp/daconoverviewofobjectspacesarchitecture.aspx OMG UML http://www.omg.org Site oficial do DePO http://sourceforge.net/projects/depo Site do DePO em Português http://www.liws.com.br/depo

Arquitetura

II
II

Arquitetura

10

2
2

Arquitetura

2.1
2.1

Como o DePO funciona

 

DePO armazena, recupera e exclui objetos em bancos de dados, todo o trabalho necesaário para que estas tarefas sejam executadas são feitos automaticamente pelo DePO.

O

DatabaseMechanism é a implementação basica da interface necessária para que este trabalho

seja feito, todo a interação com este mecanismo deve ser feita através dos mecanismos específicos para a conexão com o banco de dados, atualmente estão implementados os mecanismos ADOMechanism para comunicação utilizando ADOConnection e DBXMechanism para comunicação utilizando SQLConnection do DBExpress.

Após

DePO is able to take care itself in automatic way of the creation, loading, modernization and cancellation of objects. The objects do not have no acquaintance which it is the source or the destination of the contained information in their attributes. Different objects can recover their data from different sources, and the destination can be changed where persist the data changing with little lines of code.

Once that has been written the way in which the information must persist (the persistence mechanism), all the the one which is necessary to make in order to persist objects are to mapping the classes and the attributes with the properties of the persistence mechanism, as an example the tables and the columns that will contain the data for the mechanisms that they have to that to make with the database relational.

DePO can also persist inherited classes, persistindo the differences from the class base in connected tables. In this way it is possible to see the property common of different classes that they derive from the same class. In other words I can have a list of customers and suppliers in the same grill, and if I want to modify one of these objects, I can make creating it the correct associated type of form the class.

DePO manages in way the many simple relations between objects and the persistence of these relations conserving referencial integrity. All the classes of our colloquiano model with the mechanism of persistence or the mechanisms of persistence demanding the operation that they want to carry out through an object arbitrator the PersistenceManager, that knowing all the present mechanisms and the mappings to they it associates to you will turn the operation demanded from the class to the interested mechanism of persistence.

2.2
2.2

Classes e Propriedades

 

A declaração das classes é como de qualquer objeto no delphi com a seguinte particularidade, DePO

utiliza a RTTI do Delphi para poder acessar as propriedades publicadas.

Segundo o Delphi, e como consta no manual do tiOPF:

TPersistent encapsula o comportamento comum para todos os objetos serem acessados por outros objetos, e que podem ler e gravar suas propriedades de e para Stream. Por este proposito TPersistent introduz métodos que poder ser sobrescritos para:

Definir a procedure para carregar e armazenar dados não publicados para um stream. Prover um meio para acessar os valores das propriedades. Prover um meio para acessar o conteúdo de um objeto para outro.

11

11 DePO - Delphi Persistent Objects

DePO - Delphi Persistent Objects

11 DePO - Delphi Persistent Objects

{$M+} TPersistent = class(TObject) private procedure AssignError(Source: TPersistent); protected procedure AssignTo(Dest: TPersistent); virtual; procedure DefineProperties(Filer: TFiler); virtual; function GetOwner: TPersistent; dynamic; public destructor Destroy; override; procedure Assign(Source: TPersistent); virtual; function GetNamePath: string; dynamic; end; {$M-}

A diretiva de compilação $M, ativa antes de TPersistent e desavida após, faz com que a geração de

informações RTTI (Run Time Type Information) ocorra para TPersistent e todas as classes derivadas de TPersistent.

TdpoPersistentObject = class(TInterfacedPersistent, IdpoPersistentObject)

A classe TdpoPersistentObject é derivada de TInterfacedPersistent que por sua vez é derivada de

TPersistent.

Para que o DePO possa popular as propriedades de uma classe automaticamente, esta deve ser derivada de TPersistent ou ser compilada com a diretiva $M ativada ou derivada de outra que esteja com RTTI ativada. Mas não basta ser derivada de TPersistent é necessário que estas propriedades estejam publicadas, ou seja na sessão published da classe, como no exemplo a seguir:

type TPerson = class(TdpoPersistentObject) private FChields: TdpoRelationshipResolver; FName: String; FFatherOID: String; FID: String; function GetChields: TPeople; function GetHasChields: Boolean; public procedure AfterConstruction; override; property HasChields: Boolean read GetHasChields; published property ID: String read FID write FID; property Name: String read FName write FName; property FatherOID: String read FFatherOID write FFatherOID; property Chields: TPeople read GetChields; end;

Na classe TPerson, DePO só terá conhecimento das propriedades ID, Name, FatherOID e Chields, a propriedades HasChields, apesar de pública, não será acessivel pelo DePO, pois RTTI só contém informações published.

Arquitetura

12

2.3
2.3
Units
Units

dpoIntrospector in 'dpoIntrospector.pas',

dpoDatabaseSchema in 'dpoDatabaseSchema.pas', dpoClassMapping in 'dpoClassMapping.pas',

dpoPersistanceObject in 'dpoPersistanceObject.pas',

dpoDatabaseMechanism in 'dpoDatabaseMechanism.pas', dpoADOMechanism in 'dpoADOMechanism.pas', dpoDBXMechanism in 'dpoDBXMechanism.pas';

dpoDataSet in 'dpoDataSet.pas';

dpoRegister in 'dpoRegister.pas',

2.4
2.4

Declarando Objetos

 

Os objetos devem ser declarados como qualquer objeto no Delphi, mas o DePO apresenta duas classes básicas para facilitar o trabalho, implementando várias propriedades e métodos.

A seguir alguns exemplos de mapeamento de objetos e lista de objetos.

TCliente = class(TdpoPersistentObject) private FEmails: TdpoRelationshipResolver; published property ID: String read FID write SetID; property Nome: String read FNome write SetNome; property Emails: TEmails read GetEmails; end;

TEmails = class(TdpoPersistentObjectList) public function Add: TEmail; property Items[const Index: Integer]: TEmail read GetItem; default; end;

TEmail = class(TdpoPersistentObject) published property ID: String read FID write SetID; property Endereco: String read FEndereco write FEndereco; property Cliente: TCliente read GetCliente write SetCliente; property ClienteOID: String read FClienteOID write FClienteOID; end;

13

13 DePO - Delphi Persistent Objects

DePO - Delphi Persistent Objects

13 DePO - Delphi Persistent Objects
2.5 2.5.1
2.5
2.5.1

Mapeamento de Objetos

De Objetos para RDB

Conceitos Fundamentais de Mapeamento de objetos para banco de dados relacionais, por Marcos Barreto

A maioria das modernas aplicações é projetada para o uso da tecnologia orientada a objeto e bancos

de dados de relacionais (RDBs) para armazenar os dados. Isto não que dizer que não haja outras opções, é por que existem muitas aplicações construídas com linguagens procedurais e muitos

sistemas usarão bancos de dados orientados a objeto ou arquivos XML para armazenar os dados. Há uma incompatibilidade (impedance mismatch) entre a tecnologia orientada a objeto e a

relational, tecnologias estas que são geralmente usadas para construir os softwares. É muito fácil superar esta incompatibilidade, o segredo é: você precisa entender o processo de mapeamento de objetos para RDBs e você precisa entender como implementar estes mapeamentos. Neste capítulo

o termo "mapeamento" será usado para fazer referência a como um objeto e seus relacionamentos podem ser mapeados para uma tabela e seus relacionamentos no banco de dados. Logo você descobrirá que a coisa não é tão simples como soa. Conceitos Básicos

Quando aprendendo a mapear objetos para RDB's o ponto inicial para começar é com os atributos de uma classe. Um atributo será mapeado para zero ou mais colunas em um RDB. Lembre-se que nem todos os atributos são persistentes, alguns são usados para cálculos temporários. Por exemplo, um objeto "Estudante" pode ter um atributo Media que é necessário dentro de sua aplicação mas não

é necessário ao banco de dados porque ele é calculado pela aplicação. Outra questão é que um

atributo de uma classe pode ser também uma outra classe (Classe complexa), temos como exemplo um objeto "Cliente" tem um objeto "Endereço" como um atributo - isto realmente reflete uma associação entre as duas classes que precisaria ser provável mapeada, e os atributos da própria classe "TEndereço" precisarão ser também mapeados. O mais importante é que tudo isto é uma

definição recursiva: Em algum ponto o atributo será mapeado para zero ou mais colunas.

A mapeamento mais fácil que você terá é uma propriedade mapeada de um simples atributo a uma

simples coluna. É até mais simples quando ambos tem os mesmos tipos básicos, por exemplo: elas

são ambas datas, o atributo é uma string e a coluna é um char, ou o atributo é um número e a coluna

é um float.

Pode ser mais fácil pensar que classes são mapeadas em tabelas, e de certo modo elas são, mas nem sempre isso acontece de forma direta. Com exceção de bancos de dados muito simples você nunca terá um mapeamento um-para-um de classes para tabelas, isto será visto neste capítulo quando for visto o mapeamento de heranças.

A medida que for explicando sobre mapeamento darei exemplo de como isto é feito no DePO. Todos

os exemplos estarão disponíveis no diretório DePO\Demos e para simplificar somente será mostrado

aqui o código realmente necessário para demonstrar a parte de mapeamento do framework.

Vou colocar este código aqui no lugar assim que tiver montado os outros exemplos.

Vamos começar com um simples exemplo que fará o mapeamento de um objeto para uma tabela. Simples exemplo de mapeamento no Depo Neste exemplo nós temos uma classe chamada TArtigo que é mapeado para uma tabela chamada ARTIGO e os atributos também são mapeados diretamente para campos um a um. Vamos a classe:

TArtigo = class(TdpoPersistentObject) private FOID: string; FNome: string; FPreco: extended; published property OID: string read FOID write FOID; property Nome: string read FNome write FNome;

Arquitetura

14

property Preco: extended read FPreco write FPreco; end;

Veja que todos os atributos estão published, isto porque o framework usa o Delphi RTTI (RunTime type information) para recuperar os dados contidos nos atributos quando estiver persistindo. Veja agora o mapeamento:

procedure TAppManager.InitializeModel; begin

with MappingManager.Classes.Add do begin ClassObject:= TArtigo; StorageName:= 'ARTIGO'; with AttributesMapping do begin Add('OID', 'OID', 38, True); Add('Nome', 'NOME', 50).Required:= True; Add('Preco', 'PRECO'); end; end;

end;

Como você pode perceber é bem simples. O método Add, como ele está sendo chamado aqui, retorna um objeto do TdpoDBAttributeMapping e tem como parâmetros:

function TdpoDBAttributeMappingCollection.Add(AAttributeName, AColumnName:

String; ASize: Integer = 0; AIsOID: Boolean = False):

TdpoDBAttributeMapping;

Exemplo: Depo\Demos\01-Mapping_Basic Mapeamento com associação 1-1 Como um exemplo deste simples tipo de associação nós escolhemos criar uma nova classe chamada TGrupo e então fazer com que TArtigo tenha uma referência a esta classe. Esta associação só é navegável do Artigo para seu Grupo. Ambas as classes são modeladas no seguinte diagrama de classe (este diagrama não mostra métodos propositalmente por não ser relevante ao processo de mapeamento).

Diagrama aqui

Algumas coisa no exemplo ainda estão obscuras assim que verificar detalharei o resto da parte de mapeamento.

Mapeando Estruturas de herança Mapeando uma hierarquia para uma simples tabela Mapeando cada classe concreta para sua própria tabela Mapeando cada classe para sua própria tabela Mapeando Classes para uma estrutura de uma Tabela genérica Mapeando herança múltipla Comparando as estratégias Mapeando relacionamentos de objetos Tipos de relacionamentos

15

15 DePO - Delphi Persistent Objects

DePO - Delphi Persistent Objects

15 DePO - Delphi Persistent Objects

One-to-one, One-to-many, Many-to-many Uni-directional, Bi-directional Como os relacionamentos de objetos são implementados Como os relacionamentos do RDB são implementados Mapeando coleções ordenadas Mapeando relacionamentos recursivos Ajuste de peformance Ajustando seus Mapeamentos Consultas por demanda (Lazy Reads)

2.5.2
2.5.2

Mapeando Objetos

O objeto que armazena o mapeamento de classes, atributos e seus relacionamento é

TdpoDBMappingManager

var MappingManager: TdpoDBMappingManager;

dpoDBMM:= TdpoDBMappingManager.Create(Self);

O mapeamento pode ser feito diretamente na IDE do Delphi ou através de código, o exemplo a

seguir utiliza as classes apresentadas no tópico Declarando Objetos via código.

12

para mostrar o mapeamento

A classe TCliente possui a propriedade TEmail, vejamos a seguir o mapeamento destas classes:

with MappingManager.Classes.Add do begin ClassObject:= TCliente; StorageName:= 'CLIENTE'; with AttributesMapping do begin Add('Id', 'ID', 38, True); Add('Nome', 'NOME', 50).Required:= True;

with RelationshipsMapping.Add do begin RelatedClass:= TEmail; MasterAttribute:= 'Emails'; IsRetrieveCascaded:= True; IsSaveCascaded:= True; IsDeleteCascaded:= True; Bindings.Add('Id=ClienteOID'); end; end;

with MappingManager.Classes.Add do begin ClassObject:= TEmail; StorageName:= 'EMAIL'; with AttributesMapping do begin Add('Id', 'ID', 38, True); Add('Endereco', 'ENDERECO', 50).Required:= True; Add('ClienteOID', 'IDCLIENTE', 38).Required:= True; end;

Arquitetura

16

with RelationshipsMapping.Add do begin Cardinality:= OneToOne; RelatedClass:= TCliente; MasterAttribute:= 'Cliente'; Bindings.Add('ClienteOID=Id'); end; end;

with MappingManager do begin RegisterList(TEmail, TEmails); end;

O mapeamento via IDE do Delphi funciona da mesma forma, mas os valores devem ser informados no Object Inspector com a seguinte particularidade, as propriedades ClassObject e InheritsMappingFrom são do tipo TClass e o Object Inspector não tem como saber quais as classes que estão declaradas em código, para isto foram criadas as propriedades ClassObjectName e InheritsMappingFromName que são do tipo String, que quando definida utiliza o método FindClass para procurar a classe, o problema é que para que a classe deve ser registrada antes, na sessão Initialization da unit:

Initialization RegisterClasses([TCliente, TEmail, TEmails]);

ou

Initialization

RegisterClasse(TCliente);

RegisterClasse(TEmail);

RegisterClasse(TEmails);

Antes de começar a utilizar estas classes, é necessário tomar cuidado, pois qualquer referência a .Classes[Index] no evento OnCreate de um Form ou DataModule irá resultar em uma excessão, pois neste momento o Delphi ainda não carregou os objetos armazenados no Form, no método OnShow estes objetos já estarão disponíveis.

Na imagem a seguir a classe TCliente está informada no Object Inspector:

17

17 DePO - Delphi Persistent Objects

DePO - Delphi Persistent Objects

17 DePO - Delphi Persistent Objects
17 DePO - Delphi Persistent Objects 2.6 Sincronizando o banco de dados Sincronizando os objetos através
2.6
2.6

Sincronizando o banco de dados

Sincronizando os objetos através do mapeamento com o banco de dados relacional, o método ApplySchemaInfo do TdpoCustomMechanism, criará as instruções SQL necessárias para sincronizar o danco de dados de acordo com o modelo.

O método a seguir mostra como sincronizar o banco de dados com o modelo:

procedure

var SInfoDB: TSchemaInfoTables; lPos: Integer; lStrings: TStrings;

InitializeDatabase;

procedure

var lStr: String; begin lStr:= Trim(StringReplace(Value, #$D#$A, '', [])); if lStr <> '' then TdpoDBXMechanism(DBMechanism).DBXConnection.ExecuteDirect(lStr);

ExecuteSQL(Value: String);

end; begin SInfoDB:= TSchemaInfoTables.Create(TSchemaInfoTable); TdpoDatabaseMechanism(DBMechanism).Driver.GetSchemaInfo(SInfoDB);

if SInfoDB.Count = 0 then begin if (MessageBox(0, 'Deseja criar o Banco de dados?', 'Atenção', MB_ICONQUESTION or MB_YESNO) = idYes) then begin lStrings:= TStringList.Create; try TdpoDBXMechanism(DBMechanism).ApplySchemaInfo(lStrings); lPos:= 1;

Arquitetura

18

while lPos > 0 do ExecuteSQL( StrGetToken(lStrings.Text, finally lStrings.Free; end; end; end; end;

';', lPos));

Aproveitando as informações disponíveis em TSchemaInfoTables se precisarmos saber se apenas uma tabela existe no banco de dados podemos utilizar o método FindTableByName:

if SInfoDB.FindByTableName('Nome_da_Tabela') <> nil then MessageDlg('Tabela existe :)', mtInformation, [mbOK], 0);

Trabalhando com Objetos

III
III

Trabalhando com Objetos

20

3
3

Trabalhando com Objetos

 
3.1
3.1
CRUD
CRUD

Todas as operações que podem ser feitas em um objeto em relação a persistência recebe o nome de CRUD que significa Criar, Recuperar, Atualizar e Excluir (Created, Retrive, Update and Delete). Uma instância de um objetoé identificada unicamente por uma "Identificação de Objeto" conhecida como OID, o tipo de OID deve ser escolhido entre os tipos disponíveis, você pode utilizar uma string de 38 posição com uma GUID, que pode ser gerada pelo Delphi.

Todos os métodos CRUD são implementados em TdpoPersistentObject ou no Mecânismo de Persistência.

3.2
3.2

Criando

 

Supondo que temos a seguinte classe:

 

type TCliente = class(TdpoPersistentObject) private FID: String; FNome: String; procedure SetID(Value: String); procedure SetNome(const Value: String); public procedure AfterConstruction; override; published property ID: String read FID write SetID; property Nome: String read FNome write SetNome; end;

Portanto se desejamos criar um novo cliente, o seguinte código é suficiente:

var Cliente: TCliente; begin lCliente:= TCliente.Create(DBMechanism); Cliente.Nome:= 'Agostinho Francisco'; Cliente.Save;

O método Save irá tornar este objeto persistente. Note que não passamos nenhum parâmetro para a propriedade ID, a propriedade ID será comentada num capitulo exclusivo sobre OID.

3.3
3.3

Recuperando

 

Para recuperarmos um objeto, após criá-lo, passamos

var Cliente: TCliente; begin

21

21 DePO - Delphi Persistent Objects

DePO - Delphi Persistent Objects

21 DePO - Delphi Persistent Objects

Cliente:= TCliente.Create(DBMechanism); Cliente.ID:= '{86EAD56F-0D6C-4913-91CB-9E2B1DA54C3E}'; if not Cliente.Retrieve then begin MessageDlg('Cliente não existe', mtWarning, [mbOK], 0); end;

O código acima recupera o objeto com o OID igual a '{86EAD56F-0D6C-4913-91CB-

9E2B1DA54C3E}', o método Retrieve quando retorna "True" ou "False", como resultado.

3.4
3.4

Alterando

 

Para recuperarmos um objeto, após criá-lo, passamos

var Cliente: TCliente; begin Cliente:= TCliente.Create(DBMechanism); Cliente.ID:= '{86EAD56F-0D6C-4913-91CB-9E2B1DA54C3E}'; if Cliente.Retrieve then begin Cliente.Nome:= 'Agostinho Francisco Barbosa'; Cliente.Save; end;

Após recuperar um objeto, podemos alterar suas propriedades e salvar novamente.

3.5
3.5

Excluindo

 

O

método Delete marca o objeto para ser excluído a exclusão ocorre quando o método Save é

chamado.

 

var Cliente: TCliente; begin Cliente:= TCliente.Create(DBMechanism); Cliente.ID:= '{86EAD56F-0D6C-4913-91CB-9E2B1DA54C3E}'; if Cliente.Retrieve then begin Cliente.Delete; Cliente.Save; end; end;

3.6
3.6

Operações em Cascata

 

Se uma classe possui relacionamento com outras, seja OneToOne ou OneToMany, quando esta classe for acessada, o DePO automaticamente propagar esta operação a todas as classes relacionadas.

Antes precisamos informar ao DePO que ele deve fazer isto no mapeamento das classes, veja o

tópico Mapeando Objetos

15

para mais detalhes desta característica.

Trabalhando com Objetos

22

Primeiro Mapeamos a classe TCliente seus atributos e o relacionamento com TEmail, e DePOis o mesmo de TEmail para TCliente. Após entender todo o contexto, a parte de código a seguir é a informação que o DePO necessita para recuperar, salvar e excluir todos os emails quando estas operações forem executadas em TCliente.

with RelationshipsMapping.Add do begin RelatedClass:= TEmail; MasterAttribute:= 'Emails'; IsRetrieveCascaded:= True; IsSaveCascaded:= True; IsDeleteCascaded:= True; Bindings.Add('Id=ClienteOID'); end; end;

A propriedade Emails, possui o método de leitura GetEmails este método se encarregara de retornar todos os emails do cliente, mas isto só acontecerá se você acessar a propriedade Emails, se nenhuma referência a Emails for feita durante o contexto em que Cliente existir. Quando a operação for recuperar ou salvar, isto otimiza o processo, evitando o trafego de informação desnecessária, mas se a operação for exclusão, como se trata de objetos, se os objetos relacionados não foram acessados DePO não terá conhecimento de sua existência e não poderá excluí-los, isto torna a exclusão mais lenta pois primeiro precisamos recuperar os registros, marcá-los para exclusão (isto é propagado automaticamente) e em seguida concluir a operação com Save. Uma implementação de uma propriedade ou método alternativo para que o DePO gere apenas instruções SQL de exclusão e envie para que o servidor processe, dentro da mesma transação é uma boa alterativa.

Atenção

Apesar de definir algumas propriedades para recuperar, salvar e excluir em cascata, algumas particularides são características do DePO:

OneToOne implementadas entre "Objeto - Atributo", mesmo definindo IsRetrieveCascated os dados da classe relacionada serão recuperadas OneToOne em Generalização, exemplo:

TCliente = class(TPessoa) IsRetrievedCascated em TCliente define se os dados de TPessoa será recuperado ou não. OneToMany IsRetrievedCascated define se os dados da classe detalhe será recuperado ou não.

3.7
3.7

Problemas com Operações em Cascata

Para os métodos:

TPai.Filhos; TPai.Delete; //não estava apagando os filhos na tabela TPai.Save;

Se alguma operação não funciona, então provavelmente deve estar faltando alguma coisa no seu código:

1) você criou a propriedade filhos no pai?

FFilhos: TdpoRelationshipResolver;

23

23 DePO - Delphi Persistent Objects

DePO - Delphi Persistent Objects

23 DePO - Delphi Persistent Objects

2) No After construction vc criou o objeto?

procedure TPai.AfterConstruction; begin inherited; FFilhos:= Relationships.Add('Filhos'); end;

Atenção:

É comum quando adicionar os relacionamentos digitar nomes errados como:

type TInvoice = class(TdpoPersistentObject) private FItems: TdpoRelationshipResolver; public procedure AfterConstruction; override; published property Items: TItems read GetItems; end;

procedure TInvoice.AfterConstruction; override; begin inherited; FItems:= Relationships.Add('Itens'); end;

A propriedade foi declarada como "Items" e no relacionamento foi passado o texto "Itens"

3) Na coleção de Filhos vc implementou o método GetFilhos com todos os detalhes?

function TPai.GetFilhos: TFilhos; begin if FFilhos.RelatedObject = nil then begin FFilhos.RelatedObject:= TFilhos.Create(DBMechanism, Self, TFilho); FFilhos.Retrieve; end;

Result:= TFilhos(FFilhos.RelatedObject); end;

4) No mapeamento da classe pai você definiu o relacionamento, como IsRetrieveCascated?

with MappingManager.Classes.Add do begin ClassObject:= TPai; StorageName:= 'TABPAI';

with RelationshipsMapping.Add do begin RelatedClass:= TFilho;

Trabalhando com Objetos

24

MasterAttribute:= 'Filhos'; IsRetrieveCascaded:= True; IsSaveCascaded:= True; IsDeleteCascaded:= True; Bindings.Add('Id=PaiOID'); end;

5) No mapeamento do Filho, vc criou o campo em que o ID do pai será salvo para que ele possa recuperar?

with MappingManager.Classes.Add do begin ClassObject:= TFilho; StorageName:= 'TABFILHO';

lAttribute:= Add; with lAttribute do begin AttributeName:= 'PaiOId'; ColumnName:= 'PAIOID'; Size:= 38; end; end;

6) No mapeamento do filho, criou o relacionamento com o pai?

with RelationshipsMapping.Add do begin Cardinality:= OneToOne; RelatedClass:= TPai; MasterAttribute:= 'ID'; IsSaveCascaded:= False; IsDeleteCascaded:= False; IsRetrieveCascaded:= False; Bindings.Add('PaiOID=Id'); end;

Note aqui que neste relacionamento os

cascated

7) na Interface do Filho a propriedade com o pai?

TFilho = class(TdpoPersistentObject) private

FPaiOID: String; public

são falsos e a inversão dos campos no Binding

property PaiOID: String read FPaiOID write FPaiOID; end;

3.8
3.8

Listas (Coleções)

Com as classes criadas a partir de TdpoPersistentObjectList podemos gerenciar coleções de objetos, esta classe será o "Interator" para acessar cada um dos objetos que compõe a lista.

25

25 DePO - Delphi Persistent Objects

DePO - Delphi Persistent Objects

25 DePO - Delphi Persistent Objects

A interface da classe TClientes, será utilizada para acessarmos cada um dos TCliente.

interface

type TClientes = class(TdpoPersistentObjectList) private function GetItem(const Index: Integer): TCliente; reintroduce; public property Items[const Index: Integer]: TCliente read GetItem; default; function Add: TCliente; reintroduce; function New: TObject; reintroduce; end;

implementation

function TClientes.Add: TCliente; begin Result:= TCliente(inherited Add); end;

function TClientes.GetItem(const Index: Integer): TCliente; begin Result:= TCliente(inherited GetItem(Index)); end;

function TClientes.New: TObject; begin Result:= TCliente.Create(DBMechanism, Father); end;

Note que na implementação os métodos utilizados Add e New para acrescentarmos mais clientes a coleção e a propriedade "Items", para interagirmos com os itens. A propriedade "Items" deve ser utilizada como qualquer coleção de objetos que temos no Delphi como as colunas de uma DBGrid.

Associando Listas com seus Objetos e registrando Listas

PersistentObjectList:

TArtigos = class(TdpoPersistentObjectList) private function GetItem(const Index: Integer): TArtigo; reintroduce; public function Add: TArtigo; function New: TObject; reintroduce; property Items[const Index: Integer]: TArtigo read GetItem; default; end;

PersistentObject:

TArtigo = class(TdpoPersistentObject) public procedure AfterConstruction; override;

Trabalhando com Objetos

26

published property OID: string read FOID write FOID; property Nome: string read FNome write FNome; property Preco: Extended read FPreco write FPreco; end;

Registrando a lista:

MappingManager.RegisterList(TArtigo, TArtigos);

3.9 OID
3.9
OID

Por que utilizar OID? Por que utilizar GUID? No documento de Scott Ambler em http://www.agiledata.org/essays/mappingObjects.html procure por "The importance of OIDs" "A importância dos OIDs".

Cada classe deve ter uma propriedade definida como OID, não há padrão para o nome da propriedade e nem da coluna do banco de dados, no mapeamento você deve identificar quais propriedades serão OID através da propriedade IsOID.

with MappingManager.Classes.Add do begin ClassObject:= TPerson; StorageName:= 'PERSON_TABLE'; with AttributesMapping do begin lAttribute:= Add; with lAttribute do begin AttributeName:= 'Id'; ColumnName:= 'OID_Key'; IsOID:= True; IndexType:= idxUnique; Size:= 38; Required:= True; end;

end;

Mesmo quando definido qual propriedade será OID, o valor deve ser atribuido manualmente, você pode informar este valor antes de salvar o objeto ou utilizar o Evento OnNewObjectID do TdpoDBMappingManager.

Definindo OID manualmente:

var Pessoas: TPessoas; begin with Pessoas.Add do begin ID:= 1; Name:= 'John Doe'; Save end; end;

Definindo OID no evento OnNewObject ID:

27

27 DePO - Delphi Persistent Objects

DePO - Delphi Persistent Objects

27 DePO - Delphi Persistent Objects

var dpoDBMM: TdpoDBMappingManager;

procedure TfdmData.DataModuleCreate(Sender: TObject); begin dpoDBMM:= TdpoDBMappingManager.Create(Self);

with dpoDBMM do begin Name:= 'dpoDBMM'; OnNewObjectId:= dpoDBMMNewObjectId; end; end;

procedure TfdmData.dpoDBMMNewObjectId(Sender: TdpoClassMapping; KeyAttribute: TdpoAttributeMapping; var NewObjectId: Variant); var Guid: TGUID; begin CreateGUID(Guid); NewObjectId:= GuidToString(Guid); end;

3.10
3.10

Generalização

DePO torna as operações para classes generalizadas transparentes para suas classes básicas, este comportamento é útil quando derivamos uma classe de outra e criamos uma nova tabela no banco de dados para armazenar somente as informações relacionadas a esta nova classe.

Exemplo:

TPessoa = class(TdpoPersistentObject) published property ID: String read FID write FID; property Nome: String read FNome write FNome; end;

TCliente = class(TPessoa) published property LimiteDeCredito: Currency read FLimiteDeCredito write FLimiteDeCredito; end;

A classe TCliente é uma generalização de TPessoa, a seguir o mapeamento destas classes:

with MappingManager.Classes.Add do begin ClassObject:= TPessoa; StorageName:= 'PESSOA'; with AttributesMapping do begin lAttribute:= Add; with lAttribute do begin AttributeName:= 'Id';

Trabalhando com Objetos

28

ColumnName:= 'OID'; IsOID:= True; IndexType:= idxUnique; Size:= 38; Required:= True; end;

lAttribute:= Add; with lAttribute do begin AttributeName:= 'Nome'; ColumnName:= 'NOME'; Size:= 50; Required:= True; end; end; end;

with MappingManager.Classes.Add do begin ClassObject:= TCliente; InheritsMappingFrom:= TPessoa; StorageName:= 'ClIENTE'; with AttributesMapping do begin lAttribute:= Add; with lAttribute do begin AttributeName:= 'Id'; ColumnName:= 'OID'; IsOID:= True; IndexType:= idxUnique; Size:= 38; Required:= True; end;

lAttribute:= Add; with lAttribute do begin AttributeName:= 'LimiteDeCredito'; ColumnName:= 'LIMITEDECREDITO'; Required:= True; DataType:= adtCurrency; end; end; end;

No exemplo acima, com todo este código somente uma linha nos interessa:

InheritsMappingFrom:= TPessoa;

Esta linha identifica o relacionamento de generalização entre TCliente e TPessoa, somente isto é necessário para que quando criarmos um objeto TCliente e salvarmos o DePO guarde as informações nos campos respectivos de cada tabela PESSOA e CLIENTE, e quando recuperarmos ou excluirmos um objeto, o mesmo comportamente ocorrerá. Note que em ambos registros o valor do campo OID será identico pois do ponto de vista de objetos as informações pertencem a um único objeto do tipo TCliente.

29

29 DePO - Delphi Persistent Objects

DePO - Delphi Persistent Objects

29 DePO - Delphi Persistent Objects

Uma particularidade é que a propriedade ID deve ser mapeada novamente, caso contrario o DePO não saberá como efetuar resolver este relacionamento, se a propriedade não for declarada novamente, uma excessão será disparada, como o exemplo a seguir:

uma excessão será disparada, como o exemplo a seguir: Propriedades de tipo objeto, com relacionamentos em

Propriedades de tipo objeto, com relacionamentos em classes superiores devem ser declaradas novamente, senão o DePO não saberá desta relação. Por exemplo: A classe TPessoa possui a propriedade Emails: TEmails, na generalização de TCliente = class(TPessoa) a propriedade Emails existirá, mas TdpoDBMappingManager só sabera de um mapeamento entre TPessoa e TEmails, assim sendo só poderá retornar os emails de uma pessoa e nunca de um cliente.

O

exemplo a seguir mostra como deve ser este relacionamento:

with MappingManager.Classes.Add do begin ClassObject:= TCliente; InheritsMappingFrom:= TPessoa; StorageName:= 'CLIENTE';

with RelationshipsMapping.Add do begin RelatedClass:= TPessoa; MasterAttribute:= 'Emails'; IsRetrieveCascaded:= True; IsSaveCascaded:= True; IsDeleteCascaded:= True; Bindings.Add('Id=PessoaOID'); end; end;

O

mapeamento deste relacionamento é idêntico ao mapeamento feito em TPessoa, mas essencial

para que as operações com Emails sejam transparentes.

Desenvolvendo Aplicações

IV
IV

31

31 DePO - Delphi Persistent Objects

DePO - Delphi Persistent Objects

31 DePO - Delphi Persistent Objects
4 4.1
4
4.1

Desenvolvendo Aplicações

Relacionamento entre os Objetos

Como implementar o relacionamento entre TCliente e TEnderecos e TEmails

Os passos necessários são:

1) Declarar as propriedades para as coleções e seus respectivos métodos GET. 2) Declarar os campos responsáveis pelo relacionamento do tipo TdpoRelationshipResolver. 3) Implementar os métodos GET. 4) Mapear o relacionamento entre as classes 5) Registrar as listas para o controle das coleções dos detalhes

1,2) A Interface da Classe:

TCliente = class(TdpoPersistentObject) private FEmails: TdpoRelationshipResolver; FEnderecos: TdpoRelationshipResolver;

function GetEmails: TEmails; function GetEnderecos: TEnderecos; published property Emails: TEmails read GetEmails; property Enderecos: TEnderecos read GetEnderecos; end;

A implementação da criação dos relacionamentos

procedure TCliente.AfterConstruction; begin inherited; FEmails:= Relationships.Add('Emails'); FEnderecos:= Relationships.Add('Enderecos'); end;

3) A Implementação dos métodos GET:

function TCliente.GetEmails: TEmails; begin if FEmails.RelatedObject = nil then begin FEmails.RelatedObject:= TEmails.Create(DBMechanism, Self, TEmail); FEmails.Retrieve; end; Result:= TEmails(FEmails.RelatedObject); end;

function TCliente.GetEnderecos: TEnderecos; begin

Desenvolvendo Aplicações

32

if FEnderecos.RelatedObject = nil then begin FEnderecos.RelatedObject:= TEnderecos.Create(DBMechanism, Self, TEndereco); FEnderecos.Retrieve; end; Result:= TEnderecos(FEnderecos.RelatedObject); end;

4) A Implementação do mapenamento do relacionamento

with MappingManager.Classes.Add do begin ClassObject:= TCliente; StorageName:= 'CLIENTE'; with AttributesMapping do begin { O Mapeamento dos atributos será coberto em outro capítulo }

end; with RelationshipsMapping.Add do begin RelatedClass:= TEmail; MasterAttribute:= 'Emails'; IsRetrieveCascaded:= True; IsSaveCascaded:= True; IsDeleteCascaded:= True; Bindings.Add('Id=ClienteOID'); end;

with RelationshipsMapping.Add do begin RelatedClass:= TEndereco; MasterAttribute:= 'Enderecos'; IsRetrieveCascaded:= True; IsSaveCascaded:= True; IsDeleteCascaded:= True; Bindings.Add('Id=ClienteOID'); end; end;

A função RelationshipsMapping.Add adiciona um novo relacioanamento, por padrao em cada relacionamento adicionado as seguintes propriedades são definidas como padrão:

function TdpoDBRelationshipMappingCollection.Add:

TdpoDBRelationshipMapping; begin Result:= TdpoDBRelationshipMapping(inherited Add); with Result do begin Cardinality:= OneToMany; IsRetrieveCascaded:= False; IsSaveCascaded:= False; IsDeleteCascaded:= False;

33

33 DePO - Delphi Persistent Objects

DePO - Delphi Persistent Objects

33 DePO - Delphi Persistent Objects

end;

end;

5) A Implementação do Registro das listas que controlam a coleção dos itens.

with MappingManager do begin RegisterList(TEmail, TEmails); RegisterList(TEndereco, TEnderecos); end;

4.2
4.2

(Re)Utilizando os Objetos

Reaproveitamento de objetos.

Para evitar ficar criando e destruindo sem critério, e também para não ficar executando instruções SQL desnecessárias, devemos definir como trabalhar com os objetos.

Trabalhar com coleções de objetos, definindo os critérios de retorno é um bom método, assim quando definido um critério e os objetos forem recuperados, podemos apenas navegar entre eles ai invés de sempre efetuar novas consultas, isto também vale para os detalhes do objeto, ou seja, propriedades que são objetos, entao para uma classe TCliente que tem a propriedade do tipo TEnderecos, uma forma de otimizar o processo é que se os endereços ja forem recuperados, não precisamos recuperar novamente.

Esta métodologia não é apropriada para casos em que os dados podem sofre alterações constantes com origem em outra localização, desta forma devemos ter uma forma de saber se os dados estão atualizados, mas como saber sem retornar todos os dados novamente? e se retornarmos todos os dados novamente apenas precisamos atualizar a tela, mas isto gera um trafego de rede desnecessário se os dados já forem atualizados, e também a atualização de tela consumirá recursos do computador. Então o problema começa a exigir uma análise mais detalhada de cada caso, algumas soluções são:

- Criar uma propriedade TimeStamp para ao invés de trafegar todos os campos, podemos comparar

através de um único campo, e a cada vez que o registro for atualizado, atualizaremos este campo.

- Outra forma é a atualização de um campo contendo o CRC do registro.

- Em caso de estações remotas, em que haja uma conexão de internet de baixa banda, podemos

fazer o cache local com arquivos XML, trabalhando com DBExpress esta tarefa se torna muito fácil, e a cada vez que precisamos de um registro, apenas verificamos através do TimeStamp

- Outra forma seria apenas atualizar os dados antes de serem efetuadas operações no registro, o correspondente ao evento "OnBeforeEdit" do DataSet se encarregaria de chamar o método que atualizará os registros.

Nesto momento temos informações suficientes para perguntar "Como o Delphi trata isto com os DataSets?".

- Vou usar como exemplo o DBExpress/ClientDataSet (Midas) para ter um comparativo. Toda vez

que o evento OnAfterScroll é acionado, o Provider se encarrega de atualizar o registro atual, internamente o método UpdateRecord é chamado, fazendo com que o registro atual seja atualizado, este método garante que sempre os registros sejam sempre os mais atualizados possíveis, mas por outro lado o trafego de rede aumenta consideravelmente, há também outros incovenientes que

Desenvolvendo Aplicações

34

podem ocorrer dependendo do banco de dados utilizados, no caso do Interbase e do Firebird, se a aplicação estiver com uma transação ativa e o registro for alterado por outro usuário, mesmo que a transação do outro usuário esteja efetivada, enquanto a transação atual de quem estiver fazendo consulta, não estiver finalizada, o banco dados se encarrega de ter estas informações guardadas, para que por exemplo não ocorra a seguinte situação, um usuário faz uma consulta e vê os resultados na tela (DBGrid ou Pré-Visualizando o Relatório), ai quando ele manda pra impressora, alguns minutos DePOis, quando o método scroll ocorre, o registro não seja atualizado, evitando assim que o usuário não tenha um relatório impresso, diferente do que ele viu na tela.

Criando Interfaces para Usuários

V
V

Criando Interfaces para Usuários

36

5 5.1
5
5.1

Criando Interfaces para Usuários

Interfaces RAD

Com o componente TdpoDataSet você pode desenvolver interfaces como com qualquer outro descendente de TDataSet.

interfaces como com qualquer outro descendente de TDataSet. Basta apenas defiinir as propriedades Mechanism e

Basta apenas defiinir as propriedades Mechanism e ObjectClassName, exemplo:

dpoDataSet1.Mechanism:= DBMechanism; dpoDataSet1.ObjectClassName:= 'TInvoice';

dpoDataSet1.Open;

As outras operações comuns a DataSet estão disponíveis: First, Previous, Nest, Last, Insert, Delete, Post, Cancel.

O

etc

método Post fará com que os dados digitados nos componentes DBware (TDBEdit, TDBGrid, sejam armazenados nos atributos dos objetos, para que os objetos sejam persistidos você

)

deve chamar o método ApplyChanges.

dpoDataSet1.Edit;

dpoDataSet1.FieldByName('Date').AsDateTime:= Now;

dpoDataSet1.ApplyChanges;

Outras características:

Se classe de lista do objeto estiver criada e registrada, o DePO a usará, caso contrário ele criará uma lista genérica do tipo TdpoObjectList.

Em "Trabalhando com Objetos" - "Listas (Coleções)"

24
24

aprenda como criar e manter listas.

É aconselhável que se crie as listas de objetos personalizadas para as classes, pois TdpoObjectList

tratará os objetos contidos na lista de forma genérica, não conhecendo detalhes de sua implementação, como propriedades que são classes.

Critério para recuperar os dados:

Antes de chamar o método open, você pode definir critérios para recuperar os dados:

dpoDataSet1.Close;

dpoDataSet1.Criteria.Add(ctEqualTo, 'Nome', ['Criando aplicações com DePO']);

dpoDataSet1.Open;

Técnicas Avançadas

VI
VI

Técnicas Avançadas

38

6 6.1 6.1.1
6
6.1
6.1.1

Técnicas Avançadas

Tipos de dados

Imagem, Memo

Tipos de dados não comuns ou não suportados pelo banco de dados, precisam ser mapeados através dos eventos OnGetValue e OnSetValue, DePO já traz suporte para alguns tipos de dados (Imagem, Strings, GUID e Boolean), mas você pode definir seus próprios métodos.

O tipo memo pode ser utilizado para trabalhar com TStringList como: RichText, StringStream, TStrings, etc.

Mapeamento Automático

Para trabalhar com tipos de dados Imagem e Memo, utilizando o mapeamento automático, defina a propriedade TdpoDBMappingManager.AutoConvertDataValues:= [acdvImage,acdvMemo], e:

Imagem:

Defina a propriedade como TBitmap e no mapeamento, informe o DataType como adtImage

Memo:

Defina a propriedade como TStrings e no mapeamento, informe o DataType como adtMemo

Exemplo:

TCliente = class(TdpoPersistentObject) private

FFoto: TBitmap; FObservacao: TStrings; published

property Foto: TBitmap read FFoto write FFoto; property Observacao: TStrings read FObservacao write FObservacao; end;

with MappingManager.Classes.Add do begin ClassObject:= TCliente; StorageName:= 'CLIENTE'; with AttributesMapping do begin with Add('Foto', 'FOTO') do DataType:= adtImage;

with Add('Observacao', 'OBSERVACAO') do DataType:= adtMemo;

end;

end;

Mapeamento manual

39

39 DePO - Delphi Persistent Objects

DePO - Delphi Persistent Objects

39 DePO - Delphi Persistent Objects

Um exemplo para Bitmap, por Cosimo de Michele

No mapeamento devemos especificar as os métodos GET e SET que serão chamados codificar e definir e adquirir os valores dos dados em Stream (Sim, você pode usar estes métodos para criptografar campos).

with AttributesMapping.Add do begin AttributeName:= 'LogoPiccolo'; DataType:= adtImage; ColumnName:= 'LOGO16'; IndexType:= idxNone; IsOID:= False; OnGetValue:= TdpoAttributeGetSetValue.GetBitmap; OnSetValue:= TdpoAttributeGetSetValue.SetBitmap; end;

Agora devemos criar uma classe como esta:

TdpoAttributeGetSetValue = class class procedure GetBitmap(Attribute: TdpoAttributeMapping; AObject: TObject; var Value: Variant); class procedure SetBitmap(Attribute: TdpoAttributeMapping; AObject: TObject; Value: Variant); class procedure GetStringList(Attribute: TdpoAttributeMapping; AObject: TObject; var Value: Variant); class procedure SetStringList(Attribute: TdpoAttributeMapping; AObject: TObject; Value: Variant);

end;

class procedure TdpoAttributeGetSetValue.GetBitmap( Attribute: TdpoAttributeMapping; AObject: TObject; var Value: Variant); var Buffer: TStringStream; Intro: TObjectIntrospector; BMP: TBitmap; V: Variant; begin Buffer:= TStringStream.Create(''); Intro:= TObjectIntrospector.Create(AObject); try V:= Intro.AttributeValue[Attribute.AttributeName]; TVarData(V).VType:= VarByRef; BMP:= TVarData(V).VPointer; BMP.SaveToStream(Buffer); if BMP.Empty then Value:= Null else Value:= Buffer.DataString; finally Buffer.Free; Intro.Free; end; end;

class procedure TdpoAttributeGetSetValue.GetStringList(

Técnicas Avançadas

40

Attribute: TdpoAttributeMapping; AObject: TObject; var Value: Variant); var Buffer: TStringStream; Intro: TObjectIntrospector; lst: TStringList; V: Variant; begin Buffer:= TStringStream.Create(''); Intro:= TObjectIntrospector.Create(AObject); try V:= Intro.AttributeValue[Attribute.AttributeName]; TVarData(V).VType:= VarByRef; lst:= TVarData(V).VPointer; lst.SaveToStream(Buffer); if lst.Count = 0 then Value:= Null else Value:= Buffer.DataString; finally Buffer.Free; Intro.Free; end; end;

class procedure TdpoAttributeGetSetValue.SetBitmap( Attribute: TdpoAttributeMapping; AObject: TObject; Value: Variant); var Buffer: TStringStream; Intro: TObjectIntrospector; BMP: Graphics.TBitmap; V: Variant; begin Buffer:= TStringStream.Create(VarToStr(Value)); Intro:= TObjectIntrospector.Create(AObject); try Buffer.Position:= 0; V:= Intro.AttributeValue[Attribute.AttributeName]; TVarData(V).VType:= VarByRef; BMP:= TVarData(V).VPointer; BMP.LoadFromStream(Buffer); finally Buffer.Free; Intro.Free; end; end;

class procedure TdpoAttributeGetSetValue.SetStringList( Attribute: TdpoAttributeMadpping; AObject: TObject; Value: Variant); var Buffer: TStringStream; Intro: TObjectIntrospector; lst: TStringList; V: Variant; begin Buffer:= TStringStream.Create(VarToStr(Value)); Intro:= TObjectIntrospector.Create(AObject); try

41

41 DePO - Delphi Persistent Objects

DePO - Delphi Persistent Objects

41 DePO - Delphi Persistent Objects

Buffer.Position:= 0; V:= Intro.AttributeValue[Attribute.AttributeName]; TVarData(V).VType:= VarByRef; lst:= TVarData(V).VPointer; lst.LoadFromStream(Buffer); finally Buffer.Free; Intro.Free; end; end;

6.1.2 Boolean
6.1.2
Boolean

Alguns bancos de dados não suportam o tipo Boolean, assim também podemos mapear este tipo através do mapeamento automático ou manual.

O suporte ao mapeamento automático para os tipos boolean está disponível da mesma forma que os

tipos imagem, Strings e GUID, necessitando apenas que se defina o DataType como adtBoolean.

Exemplo:

TCliente = class(TdpoPersistentObject) private

FPreferencial: Boolean; published

property Preferencial: Boolean read FPreferencial write FPreferencial; end;

with MappingManager.Classes.Add do begin ClassObject:= TCliente; StorageName:= 'CLIENTE'; with AttributesMapping do begin with Add('Preferencial', 'PREFERENCIAL') do DataType:= adtBoolean;

end;

end;

O mapeamento automático depende de que a propriedade AutoConvertDataValue do componente

TdpoDbMappingManager tenha acdvBoolean ou acdvBooleanAsInteger (exclusivos), se acdvBooleanAsInteger estiver definido como True, a propriedade ColumnTypeForBoolean deve ter o valor Integer ou outro tipo numérico suportado pelo Banco de Dados e os valores salvos serão 0 para Falso e 1 para Verdadeiro, caso contrário, ColumnTypeForBoolean deverá ser CHAR(1), ou outro semelhante, e os valores serão T para verdadeiro e F para Falso.

Se for necessário os eventos OnGetValue e OnSetValue podem ser utilizados para personalizar campos boolean, basta apenas definir os métodos no mapeamento e implementá-los:

with MappingManager.Classes.Add do begin

Técnicas Avançadas

42

ClassObject:= TCliente; StorageName:= 'CLIENTE'; with AttributesMapping do begin with Add('Preferencial', 'PREFERENCIAL') do begin DataType:= adtBoolean; OnGetValue:= TCustomMaps.GetBoolean; // Class Function OnSetValue:= TCustomMaps.SetBoolean; // Class Procedure end; end; end;

TDateTime

Para Firebird e Interbase, quando mapear as classes, os campos do tipo TDateTime devem ser pré- definidos como adtTimeStamp:

with MappingManager.Classes.Add do begin ClassObject:= TPerson; StorageName:= 'PERSON_TABLE'; with AttributesMapping do begin lAttribute:= Add; with lAttribute do begin AttributeName:= 'Nascimento'; ColumnName:= 'DT_NASCIMENTO'; DataType:= adtTimeStamp; end; end; end;

Se estiver trabalhando com componentes dbware, antes de passar os valores para os TFields, teste se a propriedade não está com valor igual a zero, pois quando um TField recebe uma data de valor zero ele atribui a primeira data válida, utilizada pelo Delphi "30/12/1899"

if lCliente.DataNascimento <> 0 then cdsClientes.FieldByName('DataNascimento').AsDateTime:= lCliente.DataNascimento;

if lCliente.QualquerHora <> 0 then cdsClientes.FieldByName('QualquerHora').AsDateTime:= lCliente.QualquerHora;

Clonando Objetos

A classe TdpoPersistentObject implementa o método AssignTo como protected, desta forma todas as classes derivadas de TdpoPersistentObject poderão ser clonados.

Exemplo:

var

43

43 DePO - Delphi Persistent Objects

DePO - Delphi Persistent Objects

43 DePO - Delphi Persistent Objects

Person: TPerson; ClonedPerson: TPerson;

Person:= TPerson.Create(DBMechanism); ClonedPerson:= TPerson.Create(DBMechanism);

{ definindo as propriedades de Person }

Person.Name:= 'Cesar Romero'; Person.Email.Add('cesar@liws.com.br'); Person.Email.Add('DePO@liws.com.br');

{ Clonado Person para ClonedPerson } Person.AssignTo(ClonedPerson);

Criei um gêmeo meu :)

Isto mesmo, é como se criasemos um gêmeo, pois quando você executa o seguinte método:

ClonedPerson:= Person; ClonedPerson armazena um ponteiro para Person, ou seja Person e ClonedPerson são o mesmo objeto, se você destruir um, o outro também será excluído, pois são o mesmo.

Mas com AssignTo, você faz uma cópia idêntica, mas se uma for destruída a outra continuará intacta.

6.3
6.3

Utilizando Proxy

De acordo com Scotty Ambler, Proxy é um objeto que identifica outro, mas sem a sobrecarga que o representado tem, ou seja, em todas as propriedades. Um proxy deve conter informações suficientes para que ambos, o computador e o usuário possa identificá-lo e nada mais. Por exemplo o proxy para uma pessoa deve conter seu OID para que a aplicação possa identifica-lo e Nome e Iniciais para representa-lo e o usuário poder identificálo. Proxys são comuns em listas de objetos para que o usuário possa selecionar somente os desejados, após a seleção somente o objeto selecionado será recuperado completo. Utilizando proxy você pode reduzir a carga de informações que irão trafegar na rede.

Ao invés de representar o proxy por outro objeto, DePO implementa Proxy como uma lista de campos que você deseja, esta lista é a propriedade ProxyAttribute: TStringList da IdpoQuery, para incluir os campos. Esta implementação torna fácil a programação, dispensando a necessidade de se criar outra classe para ser proxy, de uma forma simples economizamos trafego de rede, por outro lado os objetos estão carregados na memória completos, mas sem valores nas propriedades.

Exemplo:

type TClientes = class(TdpoPersistentObjectList) private function GetItem(const Index: Integer): TCliente; reintroduce; public property Items[const Index: Integer]: TCliente read GetItem; default; function