O nosso ORM está começando a tomar forma. Ao finalizarmos o método Excluir no post anterior, já conseguimos ter uma noção do seu funcionamento.

Utilizando o Método Excluir como Modelo

Vamos então, continuar com os trabalhos. Iremos pegar a base do código do método Excluir e transportar para os dois métodos restantes, Inserir e Salvar, alterando a parte SQL de cada método. Vamos lá!

Copie e cole todo o código de Excluir para o Inserir:

function TDaoUib.Inserir(ATabela: TTabela): Integer;
var
  Comando: TFuncReflexao;
begin
  //crio uma variável do tipo TFuncReflexao - um método anônimo
  Comando := function(ACampos: TCamposAnoni): Integer
  var
    Campo: string;
    PropRtti: TRttiProperty;
  begin
    FechaQuery;
    with Qry do
    begin
      sql.Add('Delete from ' + ACampos.NomeTabela);
      sql.Add('Where');

      //percorrer todos os campos da chave primária
      ACampos.Sep := '';
      for Campo in ACampos.PKs do
      begin
        sql.Add(ACampos.Sep+ Campo + '= :' + Campo);
        ACampos.Sep := ' and ';
        // setando os parâmetros
        for PropRtti in ACampos.TipoRtti.GetProperties do
          if CompareText(PropRtti.Name, Campo) = 0 then
            begin
              ConfigParametro(Qry, PropRtti, Campo, ATabela);
            end;
      end;
    end;
    Result := ExecutaQuery;
  end;

  //reflection da tabela e execução da query preparada acima.
  Result := ReflexaoSQL(ATabela, Comando);
end;

Agora iremos alterar os comandos SQL:

function TDaoUib.Inserir(ATabela: TTabela): Integer;
var
  Comando: TFuncReflexao;
begin
  Comando := function(ACampos: TCamposAnoni): Integer
  var
    Campo: string;
    PropRtti: TRttiProperty;
  begin
    FechaQuery;
    with Qry do
    begin
      sql.Add('Insert into ' + ACampos.NomeTabela);
      sql.Add('(');

      //campos da tabela
      ACampos.Sep := '';
      for PropRtti in ACampos.TipoRtti.GetProperties do
      begin
        SQL.Add(ACampos.Sep + PropRtti.Name);
        ACampos.Sep := ',';
      end;
      sql.Add(')');

      //parâmetros
      sql.Add('Values (');
      ACampos.Sep := '';
      for PropRtti in ACampos.TipoRtti.GetProperties do
      begin
        SQL.Add(ACampos.Sep + ':' + PropRtti.Name);
        ACampos.Sep := ',';
      end;
      sql.Add(')');

      //valor dos parâmetros
      for PropRtti in ACampos.TipoRtti.GetProperties do
      begin
        Campo := PropRtti.Name;
        ConfigParametro(Qry, PropRtti, Campo, ATabela);
      end;
    end;
    Result := ExecutaQuery;
  end;

  //reflection da tabela e execução da query preparada acima.
  Result := ReflexaoSQL(ATabela, Comando);
end;

Analisando o código:

  • No Excluir, percorremos apenas os campos da chave primária. Já no Inserir, temos que percorrer todos os campos da tabela.
  • Linhas 16 a 40, é onde acabamos de uma vez por todas com a interminável rotina de ficar listando os campos e parâmetros na contrução do SQL de cada tabela. Ufa! Finalmente me livrei disso! :)
  • Linha 40, executamos nossa função, ReflexaoSql, que irá inserir o registro na base de dados.

Copie todo o código do Inserir e cole no método Salvar. Feito isso, alteramos os comandos SQL:

function TDaoUib.Salvar(ATabela: TTabela): Integer;
var
  Comando: TFuncReflexao;
begin
  Comando := function(ACampos: TCamposAnoni): Integer
  var
    Campo: string;
    PropRtti: TRttiProperty;
  begin
    FechaQuery;
    with Qry do
    begin
      sql.Add('Update ' + ACampos.NomeTabela);
      sql.Add('set');

      //campos da tabela
      ACampos.Sep := '';
      for PropRtti in ACampos.TipoRtti.GetProperties do
      begin
        SQL.Add(ACampos.Sep + PropRtti.Name + '=:'+PropRtti.Name);
        ACampos.Sep := ',';
      end;
      sql.Add('where');

      //parâmetros da cláusula where
      ACampos.Sep := '';
      for Campo in ACampos.PKs do
      begin
        sql.Add(ACampos.Sep+ Campo + '= :' + Campo);
        ACampos.Sep := ' and ';
      end;

      //valor dos parâmetros
      for PropRtti in ACampos.TipoRtti.GetProperties do
      begin
        Campo := PropRtti.Name;
        ConfigParametro(Qry, PropRtti, Campo, ATabela);
      end;
    end;
    Result := ExecutaQuery;
  end;

  //reflection da tabela e execução da query preparada acima.
  Result := ReflexaoSQL(ATabela, Comando);
end;

Durantes anos, ainda um iniciante na arte de desenvolver software (valorizando a categoria :) ), centenas (ou seriam milhares?) de vezes passei pela rotina de montar SQL na mão. Chegando a ponto de sentir uma sensação de “angústia” em meus dedos, devido a rotina que se tornara este processo. Isso durou até o dia em que eu bolei um “esquema” que me tirou esse trabalho. Mas ainda, sem ter a disposição recursos como a Nova RTTI, Generics e Métodos Anônimos (sim, somente deixei o Delphi 7 em maio deste ano :oops:). Agora com estes recursos, a vida ficou um pouco mais fácil. A possibilidade de montar um código como o que foi construído aqui, para mim, é um sonho! E meus dedos agradecem. Mas eu pergunto: pode ser melhorado?!? Bom, se sim, vamos deixar para os próximos artigos.

Abaixo, código completo de DaoUib.pas:

unit DaoUib;

interface

uses Base, Rtti, Atributos, uib, system.SysUtils;

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);
    procedure FechaQuery;

    function ExecutaQuery: Integer;
  public
    Qry: TUIBQuery;

    constructor Create(ADatabase: TUIBDataBase; ATransacao: TUIBTransaction);

    function Inserir(ATabela: TTabela): Integer;
    function Salvar(ATabela: TTabela): Integer;
    function Excluir(ATabela: TTabela): Integer;

    function InTransaction: Boolean;
    procedure StartTransaction;
    procedure Commit;
    procedure RollBack;

  end;

implementation

{ TDaoUib }

uses Vcl.forms, dialogs, System.TypInfo;

constructor TDaoUib.Create(ADatabase: TUIBDataBase; ATransacao: TUIBTransaction);
begin
  inherited Create;
  if not Assigned(ADatabase) then
    raise Exception.Create('UIBDatabase não informado!');

  if not Assigned(ATransacao) then
    raise Exception.Create('UIBTransaction não informado!');

  FDatabase := ADatabase;
  FTransacao := ATransacao;

  Qry := TUIBQuery.Create(Application);
  Qry.DataBase := FDatabase;
  Qry.Transaction := FTransacao;
end;

function TDaoUib.InTransaction: Boolean;
begin
  Result := FTransacao.InTransaction;
end;

procedure TDaoUib.StartTransaction;
begin
  FTransacao.StartTransaction;
end;

procedure TDaoUib.RollBack;
begin
  FTransacao.RollBack;
end;

procedure TDaoUib.Commit;
begin
  FTransacao.Commit;
end;

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;
end;

procedure TDaoUib.FechaQuery;
begin
  Qry.Close;
  Qry.SQL.Clear;
end;

function TDaoUib.ExecutaQuery: Integer;
begin
  with Qry do
  begin
    Prepare();
    ExecSQL;
    Result := RowsAffected;
  end;
end;

function TDaoUib.Excluir(ATabela: TTabela): Integer;
var
  Comando: TFuncReflexao;
begin
  //crio uma variável do tipo TFuncReflexao - um método anônimo
  Comando := function(ACampos: TCamposAnoni): Integer
  var
    Campo: string;
    PropRtti: TRttiProperty;
  begin
    FechaQuery;
    with Qry do
    begin
      sql.Add('Delete from ' + ACampos.NomeTabela);
      sql.Add('Where');

      //percorrer todos os campos da chave primária
      ACampos.Sep := '';
      for Campo in ACampos.PKs do
      begin
        sql.Add(ACampos.Sep+ Campo + '= :' + Campo);
        ACampos.Sep := ' and ';
        // setando os parâmetros
        for PropRtti in ACampos.TipoRtti.GetProperties do
          if CompareText(PropRtti.Name, Campo) = 0 then
            begin
              ConfigParametro(Qry, PropRtti, Campo, ATabela);
            end;
      end;
    end;
    Result := ExecutaQuery;
  end;

  //reflection da tabela e execução da query preparada acima.
  Result := ReflexaoSQL(ATabela, Comando);
end;

function TDaoUib.Inserir(ATabela: TTabela): Integer;
var
  Comando: TFuncReflexao;
begin
  Comando := function(ACampos: TCamposAnoni): Integer
  var
    Campo: string;
    PropRtti: TRttiProperty;
  begin
    FechaQuery;
    with Qry do
    begin
      sql.Add('Insert into ' + ACampos.NomeTabela);
      sql.Add('(');

      //campos da tabela
      ACampos.Sep := '';
      for PropRtti in ACampos.TipoRtti.GetProperties do
      begin
        SQL.Add(ACampos.Sep + PropRtti.Name);
        ACampos.Sep := ',';
      end;
      sql.Add(')');

      //parâmetros
      sql.Add('Values (');
      ACampos.Sep := '';
      for PropRtti in ACampos.TipoRtti.GetProperties do
      begin
        SQL.Add(ACampos.Sep + ':' + PropRtti.Name);
        ACampos.Sep := ',';
      end;
      sql.Add(')');

      //valor dos parâmetros
      for PropRtti in ACampos.TipoRtti.GetProperties do
      begin
        Campo := PropRtti.Name;
        ConfigParametro(Qry, PropRtti, Campo, ATabela);
      end;
    end;
    Result := ExecutaQuery;
  end;

  //reflection da tabela e execução da query preparada acima.
  Result := ReflexaoSQL(ATabela, Comando);
end;

function TDaoUib.Salvar(ATabela: TTabela): Integer;
var
  Comando: TFuncReflexao;
begin
  Comando := function(ACampos: TCamposAnoni): Integer
  var
    Campo: string;
    PropRtti: TRttiProperty;
  begin
    FechaQuery;
    with Qry do
    begin
      sql.Add('Update ' + ACampos.NomeTabela);
      sql.Add('set');

      //campos da tabela
      ACampos.Sep := '';
      for PropRtti in ACampos.TipoRtti.GetProperties do
      begin
        SQL.Add(ACampos.Sep + PropRtti.Name + '=:'+PropRtti.Name);
        ACampos.Sep := ',';
      end;
      sql.Add('where');

      //parâmetros da cláusula where
      ACampos.Sep := '';
      for Campo in ACampos.PKs do
      begin
        sql.Add(ACampos.Sep+ Campo + '= :' + Campo);
        ACampos.Sep := ' and ';
      end;

      //valor dos parâmetros
      for PropRtti in ACampos.TipoRtti.GetProperties do
      begin
        Campo := PropRtti.Name;
        ConfigParametro(Qry, PropRtti, Campo, ATabela);
      end;
    end;
    Result := ExecutaQuery;
  end;

  //reflection da tabela e execução da query preparada acima.
  Result := ReflexaoSQL(ATabela, Comando);
end;

end.

Para testar, será necessário criar ainda as classes responsáveis por receber nossa conexão e transação. Outro detalhe é que ainda não vimos a questão dos campos não conhecidos, como por exemplo, os campos data. Nossa tabela Teste tem um campo data que deverá ser tratado corretamente e como iremos entrar no tema Generics x Interfaces, para não alongar demais, continuamos no próximo artigo.

Atualização: 18/10/2012
Uma pequena mudança de planos. No próximo artigo, faremos logo o teste, ajustando o método que configura os parâmetros da query e assim tratar dados como, por exemplo, campos no formato data. Deixaremos o tema Generics x Interfaces para a parte 9 desta sequência.

Um abraço!

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.

9 thoughts on “Inserir e Salvar – Que tal um ORM Básico? Parte 7”

  1. Estou adorando essa abordagem, meus parabéns. Apesar de eu ter pouca experiência em OOP e essas novas técnicas do delphi xe2 estou conseguindo acompanhar legal pois a explicação é bastante detalhada, nota 10.

    1. Valeu Ricardo!

      Este é o meu objetivo com este blog. Sempre escuto desenvolvedores, que utilizam outras linguagens, falando que o Delphi não impõe um pensando abstrato, orientado a objeto. Tem alguns que acham que o Delphi nem mesmo têm os recursos necessários para se utilizar tais conceitos. Eu sei, falta-lhes conhecer a ferramenta. Tiram conclusões arbitrárias, pautadas em informações totalmente superficiais.

      Espero, sinceramente, modestamente, ser capaz de desmistificar um pouco isso. O Delphi é RAD, é! Mas os recursos estão aí, ao alance de nossas mãos. Basta utilizar.

      Abraços.

  2. Ah! Só pra ficar bem claro: alguns recursos citados no artigo não surgiram no XE2. Por exemplo, Generics já está no Delphi desde a versão 2009. Eu disse que estava alheio aos novos recursos do Delphi, visto que eu vinha utilizando, ainda, a versão 7. Ok?

  3. Parabéns pela iniciativa Luiz, muito bom o material que está montando. Fácil assimilação e muito bem detalhado na explicação do conteúdo.

  4. Luiz, primeiro, parabéns, estou seguindo o raciocinio desde a parte 1 e é bem bacana você nos brindar com o conhecimento e idéias que teve.

    Tenho alguns sistemas desenvolvidos e todos eles funcionam ótimamente bem.
    Sempre utilizei 3 camadas.
    Como sou autodidata algumas coisas específicas ainda me faltam.

    Você tem algum artigo para indicar sobre a utilizar dos termos “virtual”, “override” e “inherited” ?

    Como tenho de construir um novo sistema, quero fazê-lo com DataSnap.
    Eis a pergunta, como não quero transportar nada de sql e sim, apenas “métodos”: É possível utilizar essa mesma arquitetura na utilização com DataSnap ?

    1. Olá Thiago, obrigado pela visita.

      Com relação aos termos, virtual, override e inherited, não vou aqui colocar uma definição completa, mesmo porque você consegue isso facilmente na net, mas em resumo seria:

      Virtual: você utiliza quando quer que as classes filhas tenham a possibilidade de alterar algum método da classe pai. Por exemplo, na classe TDaoBase, do artigo 10, o método ConfigParametro é virtual, pois caso seja necessário, iremos implementar alterações nas classes filhas.

      Override: usamos quando estamos implementando na classe filha um método da classe pai. Podemos tanto inserir mudanças no método como também sobrescrevê-lo por completo. Exemplo: médodo Inserir de TDaoUib utiliza a palavra chave override;

      Inherited: ao fazermos um override de algum método, podemos indicar o local onde iremos utilizar o código feito na classe pai. Ex.: no constructor (Create), que é um override, de DaoUib utilizamos:

      constructor TDaoUib.Create(ADatabaseName: string);
      begin
        inherited Create;
      
        FDatabase := TUIBDataBase.Create(Application);
        //configurações iniciais da conexão
        with FDatabase do
        begin
          DatabaseName := ADatabaseName;
          Params.Add('sql_dialect=3');
          Params.Add('lc_ctype=ISO8859_1');
          Connected := True;
        end;
      
        FTransaction := TUIBTransaction.Create(Application);
        //configurações iniciais da transacao
        with FTransaction do
        begin
          Database := FDatabase;
          DefaultAction := etmCommit;
        end;
      
        Qry := TUIBQuery.Create(Application);
        Qry.DataBase := FDatabase;
        Qry.Transaction := FTransaction;
      end;
      

      No código acima, fizemos com que o método Create da classe pai (linha 3) fosse executado primeiro e somente depois executamos os códigos específicos do Create de DaoUib.

      Com relação ao DataSnap, a ideia aqui é demonstrar alguns recursos importantes do Delphi, ainda pouco explorados pelos desenvolvedores desta ferramenta. É algo básico, onde iremos implementar o Uib e Ibx. Eu não analisei outras possibilidades, como DbExpress por exemplo, mas creio que com o conhecimento aqui transmitido será perfeitamente possível, por quem vem acompanhando esta série, evoluir o sistema para algo que atenda às suas necessidades.

      1. Luiz obrigado pela explicação.

        Eu achei excelente a sua abordagem, principalmente porque todo o processo deixará de forma independente dos drivers de conexão, onde estes no propósito são drivers, mas que ao meu ver podem ser também utilizados com pequenas adaptações para multbancos.

        Cite o DataSnap porque estou no “limite” para tal escolha em um novo projeto de desenvolvimento e se houvesse meio de utilizar este processo que você criou e o utilizar de forma prática no DataSnap, seria sensacional ao meu ver.

        Exemplos teórico:
        – Na execução do Client (fron-end), este carregaria as classes do servidor, através e com controle via GUID, o qual você já gerou;
        – No caso, apenas os eventos de validação e informação seriam transportados junto com com todas as classes e os eventos de “select”, “update”, “insert” e “delete” estariam especificamente no servidor datasnap.

        Você saberia me dizer até onde isso é possível na realidade ?

        1. Olá Thiago

          Teoricamente e superficialmente falando, acho que é possível sim tornar a abordagem que você expôs uma realidade. Porém, somente quando partir para implementação desta ideia é que será possível conhecer os desafios para alcançar tal propósito.

          Infelizmente, você já está sem tempo para definir qual caminho tomar e eu estou envolto numa montanha de afazeres aqui. Mas seria interessante, em breve, trabalharmos o seu questionamento. Por enquanto, tenho que continuar na linha já traçada para esta série afim de manter uma coerência e não perder o objetivo inicial proposto.

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.