Sie sind auf Seite 1von 9

Caderno Didtico Lazarus IDE Pgina 70

70 Universidade Federal de Santa Maria


Anexo I

Construo de Aplicaes de Acesso a Banco de Dados Parte I (Cadastro Simples)

O objetivo deste material demonstrar possveis cdigos (enxutos e seguros) para realizar
operaes de acesso/manuteno em uma tabela com uma ou mais relaes do tipo 1xn (um para
muitos) utilizando componentes DataWare (componentes que representam automaticamente informaes
contidas em uma origem de dados. O modelo de dados utilizado neste exemplo utiliza duas tabelas
relacionadas da seguinte forma:




Para fazer a demonstrao ser utilizada uma janela de manuteno semelhante figura abaixo
com o devido tratamento dos eventos que possam ocorrer objetivando um cdigo simples que possa
funcionar para qualquer tela de cadastro com caractersticas semelhantes. De forma a organizar o cdigo
um componente ActionList ser utilizado com a criao e implementao de 7 aes (actNovo,
actLocalizar, actSalvar, actCancelar, actExcluir, actSair, actLocalizarPorDescricao).



Durante o desenvolvimento dos cdigos algumas variveis so utilizadas e precisaro ter seu
contedo substitudo de acordo com a tabela na qual a manuteno ser feita. A seguir o detalhamento
do significado de tais variveis:

Tabela: Componente do tipo SQLQuery (guia SQLdb) que contm em sua propriedade
Fields, todos os campos fsicos da tabela que tero manuteno;
CampoChave: Nome do campo chave da tabela no pode ser duplicado e que identifica um
registro unicamente (o tipo de tal campo deve obrigatoriamente ser numrico inteiro).
CampoDescricao: Campo descritivo, que serve para buscas parciais (geralmente um nome
ou uma descrio do contedo do registro).
CREATE TABLE UNIDADES
(
SIGLA CHAR(3) NOT NULL,
NOME_UNIDADE VARCHAR(60) NOT NULL
);


CREATE TABLE INGREDIENTES
(
ID_INGREDIENTE INTEGER NOT NULL,
DESCRICAO VARCHAR(100) NOT NULL,
UNIDADE CHAR(3) NOT NULL
);
Edit
DBEdit (ParentColor = true, ReadOnly = true)

DBLookupComboBox
KeyField = CampoChaveDaTabelaRelacionada (Sigla)
ListSource = DataSourceDaTabelaRelacionada
ListField = CampoDescricaoDaTabelaRelacionada (Nome_Unidade)



Caderno Didtico Lazarus IDE Pgina 71

71 Universidade Federal de Santa Maria

Abertura e Fechamento de Consultas

A manuteno de uma ou mais tabelas envolve a ativao de uma consulta (inicializao de uma
transao e busca dos dados) e o seu encerramento (liberao dos recursos e encerramento da
transao). Um cdigo para fazer tais procedimentos muito comum em uma aplicao de manuteno
de dados. Neste sentido, a seguir so apresentadas duas sub-rotinas que sero futuramente
referenciadas em diversas outras partes do cdigo.
O primeiro procedimento AbrirConsultas() responde pela ativao da transao (se a mesma
no estiver ativa) e pela abertura das consultas, iniciando pelas tabelas relacionadas e por ltimo a tabela
principal. O segundo procedimento FecharConsultas inicialmente encerra a tabela principal e as
tabelas relacionadas e por fim encerra a transao. Uma prxima chamada ao procedimento de
AbrirConsultas() far a inicializao de uma nova transao, podendo dessa forma visualizar
dados inseridos por outros usurios.

procedure AbrirConsultas();
begin //Se a transao no est ativa ento inicia ela
if not SQLTransaction.Active then SQLTransaction.StartTransaction;

if not TabelaRelacionada1.Active then TabelaRelacionada1.Open; //Se as tabelas
if not TabelaRelacionada2.Active then TabelaRelacionada2.Open; //relacionadas no esto
if not TabelaRelacionadaN.Active then TabelaRelacionadaN.Open; //ativas ento ative-as
//se a tabela no est ativa ento a ative
if not Tabela.Active then Tabela.Open;
end;

procedure FecharConsultas();
begin //se as tabelas relacionadas esto ativas ento as mesmas devem ser fechadas
if TabelaRelacionada1.Active then TabelaRelacionada1.Close;
if TabelaRelacionada2.Active then TabelaRelacionada2.Close;
if TabelaRelacionadaN.Active then TabelaRelacionadaN.Close;
//se a tabela est ativa ento ela deve ser encerrada
if Tabela.Active then Tabela.Close;
//se a transao estiver ativa a mesma deve ser encerrada
if SQLTransaction.Active then SQLTransaction.EndTransaction;
end;

Abertura e Encerramento da J anela de Cadastro

Os eventos de abertura (OnShow) e fechamento (OnClose e OnCloseQuery) de um formulrio
tambm merecem ateno. No primeiro caso o estado das tabelas envolvidas na aplicao precisa ser
ajustado (as tabelas devem ser iniciadas fechadas, aguardando uma ao do usurio). No caso do
fechamento devemos verificar se existem alteraes pendentes que no foram salvas assim como
encerrar os DataSets utilizados.

Evento OnShow

FecharConsultas();
HabilitarDesabilitarControles;

Evento OnCloseQuery

if TestaSeDesejaSalvar() <> mrCancel then
CanClose:=true
else CanClose:=false;

Evento OnClose

FecharConsultas();
Fechamento da Aplicao (ao actSair)

A ao de sada deve simplesmente
fechar o formulrio (mtodo close). As demais
verificaes ficam a cargo dos eventos
descritos anteriormente: OnClose (disparado
antes do fechamento) e OnCloseQuery
(disparado antes do fechamento para confirmar
se a aplicao ser ou no fechada).

Close;


Caderno Didtico Lazarus IDE Pgina 72

72 Universidade Federal de Santa Maria
Concorrncia e Auto-incremento de um campo chave numrico

Em geral, um campo chave inteiro no deve estar disponvel nem para o usurio informar, muito
menos alterar. Neste sentido, faz-se necessrio uma rotina que produza uma numerao sequencial. Os
SGBDs modernos oferecem mecanismos de gerao automtica destes valores (campos conhecidos
como Autoincremento ou Autonumerao), mas o uso destes recursos em algumas situaes impede que
o programador tenha o controle sobre a numerao gerada. A proposta do cdigo que ser sugerido de
produzir a partir dos prprios dados, a cada nova incluso, um valor para o campo chave.
Um detalhe importante que precisa ser observado antes da construo deste cdigo: a
possibilidade do banco de dados e/ou a tabela em questo estar sendo compartilhada e mais de um
usurio realizar acesso simultneo aos dados. Neste caso faz-se necessrio tomar o cuidado de nunca
gerar dois cdigos iguais para registros diferentes, situao essa que produziria uma duplicao de chave
primria ocasionando um erro e impedindo o salvamento do registro.
A seguir sero analisados dois cdigos, o primeiro para tabelas locais, onde no existe acesso
simultneo, ou seja, somente um usurio/aplicao realiza o acesso de cada vez. Em ambos os caso
ser utilizada uma funo para produzir e retornar o ltimo valor disponvel para ser utilizado em um novo
registro.

function BuscaUltimoCodigo(): integer;
var retorno : integer; //varivel auxiliar para guardar o cdigo novo
index_old : string; //varivel auxiliar para guardar a indexao atual
begin
//guarda o campo que indexa a tabela
index_old:=Tabela.IndexFieldNames;

//troca a indexao da tabela para o campo chave
Tabela.IndexFieldNames:=CampoChave;

AbrirConsultas(); //Inicia transao e abre as consultas

//posiciona o cursor no ltimo registro
Tabela.Last;

//guarda na varivel retorno o valor do campo chave + 1
retorno := Tabela.FieldByName(CampoChave).asInteger + 1;

//volta a indexao da tabela como a mesma estava originalmente
Tabela.IndexFieldNames:=index_old;

//se o retorno for 0 (zero - tabela vazia) ento o valor a ser retornado 1 (um)
if retorno = 0 then retorno := 1;

//A funo devolve para o cdigo que a chamou o valor de retorno
BuscaUltimoCodigo := retorno;
end;

Havendo a necessidade de realizar acessos simultneos em uma tabela, como por exemplo, em
um supermercado, onde diversos computadores esto lendo e gravando alteraes nas tabelas
simultaneamente, uma alternativa interessante para o controle da gerao de valores para o campo
chave a criao de uma tabela auxiliar que contm um campo para identificar o nome da tabela e outro
para armazenar o prximo cdigo a ser utilizado. Algo semelhante ao demonstrado na figura e no script
abaixo:





CREATE TABLE IDS_DISPONIVEIS
(
NOME_TABELA VARCHAR(30) NOT NULL PRIMARY KEY,
ULT_ID INTEGER NOT NULL
);

Caderno Didtico Lazarus IDE Pgina 73

73 Universidade Federal de Santa Maria
A seguir uma alternativa funo BuscaUltimoCodigo dessa vez considerando acessos
simultneos. Neste cdigo faz-se a utilizao de um componente SQLQuery e tambm do componente
SQLTransaction (recomenda-se a utilizao de um componente SQLTransaction adicional, e no aquele
que eventualmente j existe na aplicao). Diferentemente da funo BuscaUltimoCodigo
demonstrada anteriormente o cdigo a seguir independente de tabela, ou seja, ele ir receber como
parmetro o nome da tabela do qual se deseja produzir o prximo identificador. Este cdigo pode ficar
disponvel em uma unit de acesso geral (uma sugesto seria utilizar a unit do DataModule onde esto
configuradas as conexes ao banco de dados).

function BuscaUltimoCodigo(nome_tabela: string): integer;
const MAX_TENTATIVAS = 5; //Numero de tentativas que sero feitas
TEMPO_ESPERA = 100; //Tempo de espera em milissegundos aps cada tentativa
var ult_id : integer; //varivel para guardar e retornar o ltimo valor
tentativas : integer; //varivel para controlar o nmero de tentativas executadas
begin
tentativas:=0;
ult_id:=0;

//Enquanto o nmero de tentativas no alcanar o limite
while tentativas <= MAX_TENTATIVAS do
begin
try
try //Se a transao auxiliar estiver ativa ento ela deve ser encerrada
if SQLTransactionAux.Active then SQLTransactionAux.EndTransaction;
SQLTransactionAux.StartTransaction; //Inicia uma nova transao
//Se a query auxiliar estiver ativa ento ela deve ser fechada
if SQLQueryAux.Active then SQLQueryAux.Close;

//Limpa e adiciona ao texto da query o comando update
SQLQueryAux.SQL.Clear;
SQLQueryAux.SQL.Add('update ids_disponiveis set ult_id = ult_id + 1 ' +
'where nome_tabela = ' + QuotedStr(nome_tabela));
SQLQueryAux.ExecSQL; //executa o comando update

//Limpa e adiciona ao texto da query o comando select
SQLQueryAux.SQL.Clear;
SQLQueryAux.SQL.Add('select ult_id from ids_disponiveis ' +
'where nome_tabela = ' + QuotedStr(nome_tabela));
SQLQueryAux.Open; //executa o comando select

//Atribui a varivel ult_id o valor do campo retornado pelo select
ult_id:=SQLQueryAux.FieldByName('ULT_ID').asInteger;

SQLTransactionAux.Commit; //Encerra a transao gravando os dados
break; //Encerra o lao de repetio

except //Caso algum erro ocorra
SQLTransactionAux.Rollback; //Cancela as alteraes feitas no banco de dados

tentativas:=tentativas+1; //Incrementa o nmero de tentativas
sleep(TEMPO_ESPERA); //Aguarda o tempo configurado para tentar novamente
end;
finally //Aps concluir o cdigo (se existirem ou no erros de execuo)
//Testa se a transao no esta ativa e volta a ativa-la
if not SQLTransactionAux.Active then SQLTransactionAux.StartTransaction;
end;
//Se o n de tentativas alcanou o mximo ento gera um erro para bloquear a aplicao
if tentativas = MAX_TENTATIVAS then
raise Exception.Create('No foi possvel obter um identificador nico');
end;

//A funo devolve para o cdigo que a chamou o valor de retorno
BuscaUltimoCodigo := ult_id;
end;

Caderno Didtico Lazarus IDE Pgina 74

74 Universidade Federal de Santa Maria

Verificando Alteraes em um Registro

Um DataSet (conjunto de dados) troca de estado em diversos momentos (hora est sendo
consultada, hora est recebendo um registro, hora est editando um registro, etc.) e durante as
mudanas de estado necessrio saber o que o usurio quer fazer com as informaes que at ento
no foram salvas. Suponha, por exemplo, uma situao onde o usurio clica no boto que faz a incluso
e antes de salvar ele clica novamente sobre o boto da incluso, o que aconteceria com os dados
digitados que no foram salvos? Considerando situaes como essas, faz-se necessrio questionar o
usurio sobre o que o mesmo deseja fazer com o registro atual: salv-lo, cancelar as alteraes feitas ou
cancelar a ao que ele eventualmente tenha feito (no caso do exemplo anterior, o clique dado no boto
novo). A seguir uma funo que dependendo da situao do DataSet questiona o usurio acerca do que
fazer diante de uma situao de perda de dados no salvos. A funo retorna trs possveis valores:
mrYes (os dados devem ser salvos), mrNo (os dados no devem ser salvos) e mrCancel (a ao
disparada no deve ser executada).

function TestaSeDesejaSalvar(): integer;
var retorno : integer; //Varivel auxiliar para armazenar a resposta
begin
retorno := mrNo; //Por padro o retorno no salvar

//Se dados estiverem sendo includos ou editados
if Tabela.State in [dsInsert, dsEdit] then
begin
//Alimenta a varivel com a resposta do usurio
retorno := MessageDlg('Confirmao', 'Deseja salvar as alteraes?',
mtInformation, [mbYes, mbNo, mbCancel], 0);


if retorno = mrYes then //Se a resposta for sim
actSalvar.Execute() //executa a ao de salvar
else
if retorno = mrNo then //Se a resposta for no
begin
Tabela.Cancel; //Cancela as alteraes
FecharConsultas(); //Fecha os DataSets e encerra a transao
end;
end;
//A funo devolve para o cdigo que a chamou o valor de retorno
TestaSeDesejaSalvar := retorno;
end;

Inicializao, Validao dos Dados e Autorizao de Edio dos Controles Visuais

Comumente algumas informaes precisam ser inicializadas no ato da incluso ou ento validadas
antes de um salvamento. A seguir so propostos trs subrotinas com propsitos distintos, porm teis
nas demais aes que sero descritas posteriormente. Seu cdigo vai depender de cada situao e neste
material apenas so disponibilizados exemplos.

Procedimento para Inicializar os Campos de um Formulrio

procedure InicializarCampos;
begin
//Cdigo opcional para fazer algum tipo de inicializao.
//Exemplo ... deixar um LookupComboBox sem nenhum item selecionado:
DBLkpCmbBxUnidade.ItemIndex := -1;

//Tornar padro um determinado item de um DBRadioGroup
DBRdGrpTipo.ItemIndex := 1;
end;

Caderno Didtico Lazarus IDE Pgina 75

75 Universidade Federal de Santa Maria

Funo para Validar os Campos de um Formulrio

function ValidarCampos(): boolean;
var retorno : boolean; //varivel auxiliar para guardar o retorno da funo
begin
retorno := true; //por padro no h erros

//Se o tamanho do campo sem os espaos em branco for 0
if Length(Trim(DBEdtDescricao.Text)) = 0 then
begin
DBEdtDescricao.SetFocus; //Coloca o foco no componente com problema
retorno := false; //Atribui false para a varivel de retorno
//Mostra uma mensagem de erro
MessageDlg('A descrio obrigatria e no foi informada.', mtError, [mbOK], 0);
Abort; //Aborta a execuo do programa
end;

//Se nenhum item do ComboBox foi selecionado
if DBLkpCmbBxUnidade.ItemIndex = -1 then
begin
DBLkpCmbBxUnidade.SetFocus; //Coloca o foco no componente com problema
retorno := false; //Atribui false para a varivel de retorno
//Mostra uma mensagem de erro
MessageDlg('A unidade obrigatria e no foi informada', mtError, [mbOK], 0);
Abort; //Aborta a execuo do programa
end;

//A funo devolve para o cdigo que a chamou o valor de retorno
ValidarCampos := retorno;
end;

Procedimento para Habilitar ou Desabilitar o Acesso os Campos de um Formulrio

A propriedade Enabled dos componentes DataWare atribuda em funo do estado do DataSet
ao qual esto conectados:

procedure HabilitarDesabilitarControles;
begin
DBEdtCodigo.Enabled:=Tabela.Active;
DBEdtDescricao.Enabled:=Tabela.Active;
DBLkpCmbBxUnidade.Enabled:=Tabela.Active;
DBEdtUnidade.Enabled:=Tabela.Active;
end;

Incluso de um novo registro (ao actNovo)

Para realizar a incluso de um novo registro a nica exigncia a gerao de um valor para o
campo chave primria. No cdigo abaixo este valor armazenado em uma varivel e logo em seguida a
tabela colocada em modo de insero.

var codigo : integer; //varivel para guardar o valor da chave primria
begin
if TestaSeDesejaSalvar() <> mrCancel then //Caso a ao no tenha sido cancelada
begin
AbrirConsultas();//Abre as consultas e inicia a transao

codigo := BuscaUltimoCodigo(); //Varivel cdigo alimentada com um novo valor
// ou codigo := BuscaUltimoCodigo('NOME_DA_TABELA');

Tabela.Insert; //Coloca o DataSet em modo de insero


Caderno Didtico Lazarus IDE Pgina 76

76 Universidade Federal de Santa Maria
//Atribui ao campo chave da tabela o cdigo gerado
Tabela.FieldByName(CampoChave).asInteger := codigo;

InicializarCampos; //Realiza eventuais inicializaes

HabilitarDesabilitarControles
end;
end;

Persistncia (gravao) do registro atual (ao actSalvar)

A efetivao dos dados na base de dados deve ser feita se o estado da tabela for compatvel e se
no houver erros nos dados informados (tipos incompatveis, valores obrigatrios, etc.). O comando Post
grava os dados no DataSet e o comando ApplyUpdates envia o DataSet para ser persistido na base de
dados. Havendo sucesso nessas duas etapas os dados podem ser efetivados (commit) e em no
havendo sucesso as alteraes devem ser canceladas (rollback).

var
chave: integer; //varivel auxiliar para guardar o identificador do registro
begin
//Verifica se a o DataSet est em Insero ou Edio de dados
if Tabela.State in [dsInsert, dsEdit] then
begin
if ValidarCampos() then //Se os campos foram corretamente validados
begin
try
//Guarda o identificador do registro
chave := Tabela.FieldByName(CampoChave).Value;

Tabela.Post; //Grava os dados no DataSet
Tabela.ApplyUpdates; //Envia os dados para o Servidor de BD
SQLTransaction.Commit; //Realiza um Commit e encerra a transao ativa

AbrirConsultas(); //Inicia uma nova transao e abre as consultas

//Posiciona o DataSet no registro em que se encontrava antes do salvamento
Tabela.Locate(CampoChave, chave, []);
except
//Ocorrendo um erro do tipo Exception
on E : Exception do
begin
//Exibe a mensagem do erro
ShowMessage('No foi possvel salvar as informaes. ' + #13 +
'Ocorreram os seguintes erros ' + #13 + E.Message);

//Desfaz as alteraes feitas na base de dados
SQLTransactionPrincipal.Rollback;
end;
end;
end;

HabilitarDesabilitarControles;
end;
end;

Cancelamento das alteraes do registro atual (ao actCancelar)

O cancelamento das alteraes em um DataSet distinto durante uma Insero (onde os dados
no existiam) e em uma Edio (onde existiam valores que foram alterados). No caso do cancelamento
de uma edio os dados anteriores devem retornar. No caso do cancelamento de uma insero, o

Caderno Didtico Lazarus IDE Pgina 77

77 Universidade Federal de Santa Maria
DataSet deve ser fechado para que a ao de incluso seja novamente acionada. Em ambos os casos o
cancelamento feito nos dados contidos no DataSet sem envolver a base de dados diretamente.

//Cdigo da ao actCancelar

var sit : TDataSetState; //Varivel auxiliar para guardar o estado do DataSet
begin

sit := Tabela.State; //Captura o estado do DataSet antes do cancelamento

//Se o DataSet est em Insero ou Edio
if sit in [dsInsert, dsEdit] then
begin
//Confirma se os dados sero de fato cancelados
if MessageDlg('Confirmao', 'Tem certeza que deseja cancelar as alteraes ?',
mtInformation, [mbYes, mbNo], 0) = mrYes then
begin
//Se sim, cancela a edio no DataSet
Tabela.Cancel;

//Se o estado inicial era insero ento os DataSets e a transao sero fechados
if sit = dsInsert then FecharConsultas();
end;

HabilitarDesabilitarControles;
end;
end;

Excluso do registro atual (ao actExcluir)

A excluso de um registro pressupe que o usurio esteja posicionado sobre o registro ao qual
deseja excluir. aconselhvel solicitar uma confirmao antes da excluso.

if Tabela.Active then //Testa se a tabela est ativa
begin //Confirma se a excluo deve ser feita
if MessageDlg('Confirmao','Tem certeza que deseja excluir o registro ' +
Tabela.FieldByName(CampoDescricao).asString,
mtConfirmation, [mbYes, mbNo], 0) = mrYes then
begin
try
Tabela.Delete; //Deleta o registro no DataSet
Tabela.ApplyUpdates; //Envia as alteraes para o BD
//Efetiva as alteraes no BD
SQLTransaction.Commit;
except
on E : Exception do
begin
SQLTransaction.Rollback; //Cancela as alteraes no BD
//Exibe uma mensagem indicando que no foi possvel fazer a excluso
MessageDlg('No foi possvel excluir o registro ' + #13 +
'Possivelmente existem dados em outras tabelas ' +
'que esto relacionados a este registro.',
mtError, [mbOk], 0);
end;
end;

FecharConsultas(); //Encerra a transao e fecha os DataSets

end;

HabilitarDesabilitarControles;
end;


Caderno Didtico Lazarus IDE Pgina 78

78 Universidade Federal de Santa Maria
Localizao de Registros

A localizao de um registro normalmente feita de duas maneiras. Na primeira, o usurio,
conhecendo o valor do campo chave da tabela (um cdigo, por exemplo), o informa e o programa tenta
fazer uma busca exata. Na segunda maneira, o usurio informa parte de uma informao descritiva
acerca da tabela e a aplicao tenta fazer uma busca aproximada com os dados informados. Neste
ltimo caso, o cdigo utilizado normalmente no evento OnChange (ao mudar o contedo) de uma Edit
normal.

Busca por chave primria (actLocalizar)

var codigo : string; //Varivel auxiliar para guardar o cdigo informado
begin
//Se a ao no for cancelada
if TestaSeDesejaSalvar() <> mrCancel then
begin
try
FecharConsultas(); //Encerra a transao e os DataSets
//Solicita ao usurio que informe um cdigo
if InputQuery('Cdigo', 'Informe o cdigo que voc deseja localizar', codigo) then
begin
StrToInt(codigo); //Tenta fazer uma converso para evitar erros

//Inicia a transao e abre os DataSets
AbrirConsultas();

//Se uma busca exata no encontrar nenhum registro
if not Tabela.Locate(CampoChave, Codigo, []) then
begin
FecharConsultas();
//Mostra mensagem que o registro no foi encontrado
MessageDlg('Cdigo no encontrado!', mtWarning, [mbOk], 0);
end;

//Caso registro tenha sido encontrado o DataSet o ter posicionado
end;
except
FecharConsultas(); //Se houver algum erro, fecha o DataSet
end;

HabilitarDesabilitarControles;
end;
end;


Busca por campo descritivo (actLocalizarPorDescricao)

//Se h algo informado na edit (o tamanho sem os espaos em branco maior que zero)
if Length(Trim(EdtDescricao.Text)) > 0 then
begin
if TestaSeDesejaSalvar() <> mrCancel then //Se a ao no for cancelada
begin
//Encera e na sequncia inicia a transao e os datasets
FecharConsultas();
AbrirConsultas();

//Realiza uma busca parcial e sem distino de maisculas/minsculas
Tabela.Locate(CampoDescricao,EdtDescricao.Text,[loCaseInsensitive, loPartialKey]);
end;

HabilitarDesabilitarControles;
end;

Das könnte Ihnen auch gefallen