No artigo anterior, iniciamos a construção da classe TDaoUIB. Através de um Ctrl+Shift+C, geramos os três métodos abaixo:
[sourcecode language=”delphi”]
function TDaoUib.Inserir(ATabela: TTabela): Integer;
begin
end;
function TDaoUib.Salvar(ATabela: TTabela): Integer;
begin
end;
function TDaoUib.Excluir(ATabela: TTabela): Integer;
begin
end;
[/sourcecode]
Vamos então, neste artigo, implementar o método Excluir.
Método Excluir de TDaoUib
Já criamos os métodos responsáveis por pegar o nome da tabela e os campos da chave primária. Agora, observe a declaração do método Excluir da unidade DaoUib.pas:
[sourcecode language=”delphi”]
function TDaoUib.Excluir(ATabela: TTabela): Integer;
begin
end;
[/sourcecode]
Nele, estou recebendo um parâmetro do tipo TTabela. Desta forma, como eu já terei o nome da tabela, bastará definir quais os tipos dos campos da chave primária, ou seja, se é um campo string, inteiro, data, etc.
Diante destas informações, o método Excluir fica assim definido:
[sourcecode language=”delphi”]
function TDaoUib.Excluir(ATabela: TTabela): Integer;
var
NomeTab: string;
CamposPk: TResultArray;
Sep: string; //separador
Campo: string;
Contexto : TRttiContext;
TipoRtti : TRttiType;
PropRtti : TRttiProperty;
begin
NomeTab := PegaNomeTab(ATabela);
CamposPk := PegaPks(ATabela);
Contexto := TRttiContext.Create;
try
TipoRtti := Contexto.GetType(ATabela.ClassType);
with Qry do
begin
close;
SQL.Clear;
SQL.Add(‘Delete from ‘ + NomeTab);
SQL.Add(‘Where’);
Sep := ”;
// percorrer todos os campos da chave primária
for Campo in CamposPk do
begin
SQL.Add(Sep + Campo + ‘= :’ + Campo);
Sep := ‘ and ‘;
// setar o valor de cada parâmetro
for PropRtti in TipoRtti.GetProperties do
if CompareText(PropRtti.Name, Campo) = 0 then
begin
case AProp.PropertyType.TypeKind of
tkInt64, tkInteger:
begin
Params.ByNameAsInteger[ACampo] :=
AProp.GetValue(ATabela).AsInteger;
end;
tkChar, tkString, tkUString:
begin
Params.ByNameAsString[ACampo] :=
AProp.GetValue(ATabela).AsString;
end;
tkFloat:
begin
Params.ByNameAsCurrency[ACampo] :=
AProp.GetValue(ATabela).AsCurrency;
end;
tkVariant:
begin
Params.ByNameAsVariant[ACampo] :=
AProp.GetValue(ATabela).AsVariant;
end;
else
raise Exception.Create(‘Tipo de campo não conhecido: ‘ +
AProp.PropertyType.ToString);
end;
end;
end;
// executa delete
Prepare();
ExecSQL;
Result := RowsAffected;
end;
finally
Contexto.free;
end;
end;
[/sourcecode]
Analisando o código
- No início do código (linha 12-18), pegamos o nome da tabela, criamos um contexto e pegamos o TipoRtti.
- Da linha 20 a 25, preparamos nossa query com os comandos SQL.
- Linha 28 é o início do loop em todos os campos da chave primária.
- Na linha 33, fazemos um loop em todas as propriedades do objeto passado no parâmetro, comparando (linha 34) se o nome da propriedade é igual ao nome do campo da chave primária.
- Se sim, iremos definir (linha 36-52) o tipo (TypeKind) do parâmetro (Params.ByNameAsCurrency) de cada campo.
- Se o tipo não for encontrado, teremos um exceção na linha 58.
- Na linha 65, executamos a nossa query.
- O Result da função (linha 66) irá retornar a quantidade de registros afetados.
Olhando para a relação dos tipos informados no case (linha 36-52), veja que faltam alguns campos, como por exemplo, TDateTime e Blob. Segure o Ctrl e clique em cima de TypeKind na linha 36. Iremos acessar a unidade Rtti:
[sourcecode language=”delphi”]
…
public
function ToString: string; override;
property Handle: PTypeInfo read GetHandle;
// QualifiedName is only available on types declared in interface section of units;
// i.e. IsPublicType is true.
property QualifiedName: string read GetQualifiedName;
property IsPublicType: Boolean read GetIsPublicType;
property TypeKind: TTypeKind read GetTypeKind;
// The size of a location (variable) of this type.
property TypeSize: Integer read GetTypeSize;
property IsManaged: Boolean read GetIsManaged;
…
[/sourcecode]
Vemos que o TypeKind é do tipo TTypeKind. Segure novamente o Ctrl e clique em cima de TTypeKind. Acessamos a unidade TypInfo:
[sourcecode language=”delphi”]
…
type
TTypeKind = (tkUnknown, tkInteger, tkChar, tkEnumeration, tkFloat,
tkString, tkSet, tkClass, tkMethod, tkWChar, tkLString, tkWString,
tkVariant, tkArray, tkRecord, tkInterface, tkInt64, tkDynArray, tkUString,
tkClassRef, tkPointer, tkProcedure);
[/sourcecode]
Note que na relação não tem definido, por exemplo, o formato para campos data.
E aí você se pergunta: Como assim? Não tem um tipo para o formato data? Se é assim, como irei gravar campos deste tipo na minha tabela?
No Delphi, o tipo TDateTime é Double – TypeKind = tkFloat. Mas não se preocupe com isso agora, visto que veremos como resolver este pequeno problema quando estivermos implementando o método Inserir.
Evitando repetição de código
Bom, poderíamos dizer que o método Excluir está implementado. Mas peço que observe mais uma vez o código. Vou lhe dar 5 segundos… 4… 3… 2… 1.
Percebeu algo? Sim? Não?
Tenho certeza que você percebeu que o case com a definição dos parâmetros da query não só será utilizado no método Excluir, como também no método Inserir e Salvar, não é mesmo? Sendo assim, se seguirmos este pensamento, iremos repetir o mesmo código nestes três métodos.
Para resolver, vamos recortar o case do método Excluir, e no seu lugar chamar uma nova procedure:
function TDaoUib.Excluir(ATabela: TTabela): Integer;
var
NomeTab: string;
CamposPk: TResultArray;
Sep: string; //separador
Campo: string;
Contexto : TRttiContext;
TipoRtti : TRttiType;
PropRtti : TRttiProperty;
begin
NomeTab := PegaNomeTab(ATabela);
CamposPk := PegaPks(ATabela);
Contexto := TRttiContext.Create;
try
TipoRtti := Contexto.GetType( ATabela.ClassType );
with Qry do
begin
close;
SQL.Clear;
sql.Add(‘Delete from ‘ + NomeTab);
sql.Add(‘Where’);
//percorrer todos os campos da chave primária
Sep := ”;
for Campo in CamposPk do
begin
sql.Add(Sep+ Campo + ‘= :’ + Campo);
Sep := ‘ and ‘;
// setando os parâmetros
for PropRtti in TipoRtti.GetProperties do
if CompareText(PropRtti.Name, Campo) = 0 then
begin
ConfigParametro(Qry, PropRtti, Campo, ATabela); // <-- recortamos o case e no seu lugar inserimos uma procedure
end;
end;
//executa delete
Prepare();
ExecSQL;
Result := RowsAffected;
end;
finally
Contexto.free;
end;
end;
[/sourcecode]
Declare a nova procedure no private da classe:
[sourcecode language=”delphi”]
…
type
TDaoUib = class(TInterfacedObject, IDaoBase)
private
FDatabase: TUIBDataBase;
FTransacao: TUIBTransaction;
// Este método configura os parâmetros da AQuery.
procedure ConfigParametro(AQuery: TuibQuery; AProp: TRttiProperty; ACampo: string; ATabela: TTabela); <-- aqui
public
...
[/sourcecode]
Dê Ctrl+Shift+C para gerar o método, e em seguida, implemente o código do case:
[sourcecode language=”delphi”]
procedure TDaoUib.ConfigParametro(AQuery: TUIBQuery; AProp: TRttiProperty;
ACampo: string; ATabela: TTabela);
begin
with AQuery do
begin
case AProp.PropertyType.TypeKind of
tkInt64,
tkInteger:
begin
Params.ByNameAsInteger[ACampo] :=
AProp.GetValue(ATabela).AsInteger;
end;
tkChar,
tkString,
tkUString:
begin
Params.ByNameAsString[ACampo] :=
AProp.GetValue(ATabela).AsString;
end;
tkFloat:
begin
Params.ByNameAsCurrency[ACampo] :=
AProp.GetValue(ATabela).AsCurrency;
end;
tkVariant:
begin
Params.ByNameAsVariant[ACampo] :=
AProp.GetValue(ATabela).AsVariant;
end;
else
raise Exception.Create(‘Tipo de campo não conhecido: ‘ +
AProp.PropertyType.ToString);
end;
end;
[/sourcecode]
Pronto! Assim, evitamos a repetição desta parte. Poderemos utilizar este mesmo código no método Inserir e Salvar.
Para encerrar, proponho um pequeno desafio
Estamos chegando ao final deste artigo, mas antes quero que novamente analise o método Excluir. Vou deixar um pequeno desafio:
Tem alquma coisa no código que não está lhe agradando?
Será que, ao projetarmos a base deste código para os métodos Inserir e Salvar, não percebemos algo que poderia ser melhorado?
Fica o questionamento. Comentários são sempre bem-vindos!
Abraços e até a próxima!
O artigo e o melhor que já vi, somente uma consideração nas instruções sql eu gosto de utilizar o recurso de parâmetros pois consiste em uma boa pratica de programação.