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 é DoubleTypeKind = 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;
&#91;/sourcecode&#93;
</div>

Declare a nova procedure no <strong><em>private </em></strong>da classe:

...
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
...
&#91;/sourcecode&#93;

Dê <strong>Ctrl+Shift+C</strong> para gerar o método, e em seguida, implemente o código do <strong><em>case</em></strong>:

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!

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

One thought on “Método Excluir em TDaoUIB – Que tal um ORM Básico? Parte 5”

  1. 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.

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

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