No artigo anterior, iniciamos a construção da classe TDaoUIB. Através de um Ctrl+Shift+C, geramos os três métodos abaixo:
function TDaoUib.Inserir(ATabela: TTabela): Integer; begin end; function TDaoUib.Salvar(ATabela: TTabela): Integer; begin end; function TDaoUib.Excluir(ATabela: TTabela): Integer; begin end;
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:
function TDaoUib.Excluir(ATabela: TTabela): Integer; begin end;
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:
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;
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:
... 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; ...
Vemos que o TypeKind é do tipo TTypeKind. Segure novamente o Ctrl e clique em cima de TTypeKind. Acessamos a unidade TypInfo:
... type TTypeKind = (tkUnknown, tkInteger, tkChar, tkEnumeration, tkFloat, tkString, tkSet, tkClass, tkMethod, tkWChar, tkLString, tkWString, tkVariant, tkArray, tkRecord, tkInterface, tkInt64, tkDynArray, tkUString, tkClassRef, tkPointer, tkProcedure);
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] </div> Declare a nova procedure no <strong><em>private </em></strong>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ê <strong>Ctrl+Shift+C</strong> para gerar o método, e em seguida, implemente o código do <strong><em>case</em></strong>: [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;
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.