Ok, Ok! Somente na tecno… ops! Canal errado!

Como todos já estão cansados de saber, eu utilizo o projeto desta série em alguns trabalhos meus. E nestes, já algum tempo não sei o que são “inserts“, “updates” nem “deletes“, bem como não lembro do meu último “ParamByName” e muito menos “FieldByName“. Ponto para o nosso ORM Básico.

Porém, o ser humano é insaciável e sempre quer mais. E uma das prioridades que surgiram aqui, foi com relação à geração da classe. Para tal, eu vinha fazendo manualmente, ou seja, criava uma unit e então, a classe com todos as suas propriedades e atributos (nome da tabela, chaves primárias, etc.). E você, caro leitor, há de convir comigo que desenhar uma classe que representa uma tabela é uma coisa, mas quando isso passa a ser rotina, torna-se demasiadamente cansativo.

Foi então que eu resolvi tirar um tempinho e criar essa nova funcionalidade. Abaixo faço um resumo dos principais pontos. Ressaltando que, foi necessário fazer uma reformulação dos nomes das units visando melhorar a organização e evitar qualquer problema com outros projetos que tenho em minha máquina.

Novo Projeto no Grupo

Adicionado ao grupo de projetos um novo projeto para teste de geração de classe: ProjGerarClasse, com um formulário:

formGerarClasse

Contém:

  • Panel no topo com 2 Labels;
  • ListBox alinhando à esquerda;
  • Memo ao client;
  • Panel no rodapé com 3 Buttons.

Diagrama Gerar Classe

PrsGerarClasse

Antes de partir para o primeiro begin, eu sempre coloco no papel o que pretendo criar. Penso muito, principalmente na arquitetura das classes, e ainda assim, depois de finalizado o processo, sempre volto para refatorar alguma coisa (você é testemunha do que falo, ;) ). Analisemos o diagrama acima:

  • Demonstro de forma simples a utilização da injeção de dependência, tornando nosso código menos acoplado. Vemos isso na classe TGerarClasse. Nela, passamos o banco a ser utilizado (no caso criei uma para o Firebird – TGerarClasseBancoFirebird) no seu constructor, mas não diretamente, e sim através de uma interface. Veja que não tem ligação direta com a classe concreta do banco. Assim, poderemos criar uma para o SQL Server, por exemplo, e apenas adicionar ao projeto. Pronto! Não teremos qualquer problema para instanciá-la.
  • Descendo para as classes filhas, TGerarClasseIBX e TGerarClasseFireDac, foi onde me mantive por mais tempo. Visto que estava realmente inclinado a utilizar composição em vez de herança. Porém, são funções idênticas, ou seja, dentro da classe filha teria uma “gerar alguma coisa”, que seria um objeto de TGerarClasse, o que pareceu ser redundante. Devido a isso, preferi ficar com herança neste caso. Quem sabe futuramente eu volte e reavalie minha decisão.
  • Em TDaoIBX e TDaoFireDac, inseri um novo método, GerarClasse, que instancia a classe concreta da respectiva suíte de componentes.

Implementação

Analisar é bom, porém, bom mesmo é codar!

Vamos direto ao ponto, ou seja, TGerarClasse:

  TGerarClasse = class
  private
    function Capitalize(ATexto: String): string;
  protected
    Resultado: TStringList;
    GerarClasseBanco: IBaseGerarClasseBanco;

    FTabela,
    FUnit,
    FClasse: string;

    function GetCamposPK: string; virtual; abstract;

    procedure GerarCabecalho;
    procedure GerarFieldsProperties; virtual; abstract;
    procedure GerarRodape;
  public
    constructor Create(AClasseBanco: IBaseGerarClasseBanco);
    destructor Destroy; override;

    function Gerar(ATabela, ANomeUnit: string;
      ANomeClasse: string = ''): string;
  end;

Como pode ser visto na sua declaração, temos tudo o que é preciso, ou seja, temos métodos para gerar o cabeçalho, os “fields” e “properties” e o rodapé da unit. Por ser uma classe abstrata, delega o que é específico de cada classe filha e implementa métodos comuns, como por exemplo:

procedure TGerarClasse.GerarCabecalho;
begin
  Resultado.clear;
  Resultado.add('unit ' + FUnit + ';');
  Resultado.add('');
  Resultado.add('interface');
  Resultado.add('');
  Resultado.add('uses');
  Resultado.add('  Lca.Orm.Base, Lca.Orm.Atributos;');
  Resultado.add('');
  Resultado.add('type');
  Resultado.add('  [attTabela(' + QuotedStr(FTabela) + ')]');
  Resultado.add('  T' + FClasse + ' = class(TTabela)');
end;

O cabeçalho da unit será sempre o mesmo, não se altera nas classes filhas. Da mesma forma, o método GerarRodape:

procedure TGerarClasse.GerarRodape;
begin
  Resultado.Add('  end;');
  Resultado.Add('');
  Resultado.Add('implementation');
  Resultado.Add('');
  Resultado.Add('end.');
end;

Agora vamos analisar a classe TGerarClasseIBX:

TGerarClasseIBX = class(TGerarClasse)
  private
    FDao: TDaoIBX;
  protected
    function GetCamposPK: string; override;

    procedure GerarFieldsProperties; override;

  public
    constructor Create(AClasseBanco: IBaseGerarClasseBanco; ADao: TDaoIBX);
  end;

Nesta, o destaque ficar por conta do constructor, cuja função é a de receber o objeto que implementa a interface IBaseGerarClasseBanco para repassar ao construtor da classe pai (TGerarClasse) e também receber o objeto Dao, que neste caso em questão, é o TDaoIBX. Não será necessário colocar o código do TGerarClasseFireDac, visto que o que muda é apenas o objeto (TDaoFireDac em vez de TDaoIBX).

Implementação do método GerarFieldsProperties (gerar as propriedades e fields da classe):

procedure TGerarClasseIBX.GerarFieldsProperties;
var
  Ds: TDataSet;
begin
  inherited;

  Ds :=  FDao.ConsultaSql(GerarClasseBanco.GetSQLCamposTabela(FTabela));

  GerarClasseBanco.GerarFields(Ds, Resultado);

  GerarClasseBanco.GerarProperties(Ds, Resultado, GetCamposPK);
end;

Nele, chamamos o objeto instanciado no construtor, relativo ao banco de dados selecionado, parar gerar os Fields e Properties.

Para finalizar, segue código do botão Gerar do formulário:

procedure TfrmConverte.btnGerarClick(Sender: TObject);
var
  Unidade,
  Tabela,
  Classe: string;
begin
  if lbTabelas.Count = 0 then
    Exit;

  Tabela  := trim(lbTabelas.Items[lbTabelas.ItemIndex]);
  Unidade := Tabela;
  Classe  := Tabela;

  memResult.lines.Text := dmPrin.Dao.GerarClasse(Tabela, Unidade, Classe);
end;

Eu coloquei o mesmo nome para tabela, unit e classe, mas poderiam ser diferentes caso eu desejasse.

Executando, selecionando uma tabela e clicando no botão gerar, obtemos:

GerarClasseExecuta

Para ver o código completo, basta baixar os fontes no GitHub.

Abraços.

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.

10 thoughts on “Gerar Classes – Que tal um ORM Básico? Parte 17”

  1. Prezado Luiz com esse último artigo está encerrado à série? Cai nos artigos procurando conceitos orm no celular mas já sei que é o que procuro .Agora vou ler toda a série para ver se já me atende.um grande abraço e parabens por dividir o conhecimento.

  2. Bom dia Luiz.
    Em primeiro lugar, obrigado pela sua dedicação a um assunto que que facilita em muito a vida do desenvolvedor.
    Gostaria de saber se neste ORM básico é possivel fazer um CRUD com mestre detalhe, ou seja Uma venda e seus itens em um mesmo insert.
    Analisando o código parece-me que a operação é realizada com apenas uma entidade por vez, ou seja Insiro os dados da venda, grava e depois insiro os itens e gravo. Seria isto mesmo ou estou enganado.
    Assim caimos no clássico caso da transação bancária.
    Se puder me orientar, ficarei muito grato.

    1. Olá José,

      A ideia de fazer o Orm Básico para o Blog surgiu devido a uma necessidade que eu tive: o processo de gerar os SQL e setar seus parâmetros, isso quase que a maior parte do tempo de desenvolvimento que eu dispunha. Para mim, resolveu. Talvez para sua necessidade seja necessário implementar algo mais elaborado.

      Porém, aqui se por ventura surgir um caso como o que você descreveu, eu criaria uma uma venda (TVenda), uma lista de itens da venda ( TList<TItemVenda> ) e na hora da salvar, eu faria:

      procedure SalvarDadosPendentes;
      var
        Item: TVendaItem;
      begin
        Dao.StartTransaction;
        try
          Dao.Salvar(Venda);
          for item in ListaItens do
            Dao.Salvar(Item);
          Dao.Commit;
          ShowMessage('Dados foram salvos!');
        except
          on E: Exception do
          begin
            Dao.Rollback;
            ShowMessage('Erro: ' + E.Message);
          end;
        end;
      end;
      

      Obs.: O exception genérico desconsidere, foi apenas um exemplo.

      Espero que lhe traga uma luz.

  3. Olá! Tudo bem? Estou estudando o uso de um ORM próprio e seu blog foi o melhor que encontrei até agora. Além do passo-a-passo, tem bastante explicação. Obrigado pela contribuição.
    Escrevo além de parabenizar pela iniciativa (eu sei que um pouco tardia, aprox. 2 anos após o início), estou com dificuldades na conexão. Instalei e reinstalei o Firebird (2.5.8) para efetuar os devidos testes e fazer uma adaptação para minha necessidade que seria SQL Server e SQLite (apenas para configurações e personalizações)… No caso seria o módulo de gerar classes que estou testando, e ocorre erro “Unavailable database” (nesta Linha: “Dao := TDaoIBX.Create(ibdatabase1, texec);”. Porém se uso a mesma configuração em um FDConnection, em outras chamadas de testes, funciona. Somente o componente IBx que está dando erro. Será que já viu ocorrer isso e teria uma dica/solução para continuar os testes aqui e no futuro eu pretendo ampliar para estes dois outros BD e até gostaria de disponibilizar os códigos após sucesso (SQLite eu já fiz alterações, mas como não consegui nem testar o FB, parei).
    Obrigado pela atenção e bons códigos ai!

    1. Olá Fábio

      O tempo passa… já tem um tempo que não dou uma olhada nestes fontes por estar envolvido em alguns projetos aqui.

      Não entendi bem. Você diz que está tendo problemas com o método GerarUnit? Você está conectando pelo IBX quando tenta manipular dados? Dá o mesmo erro?

      Pergunto porque, no meu trabalho diário, venho utilizando tanto para gerar as units automaticamente quanto para o crud, sem problema algum.

      Me envie seus fontes para que eu possa entender melhor.

      1. Boa noite, meu caro! Obrigado pela rápida resposta!
        Felizmente eu consegui resolver minha dúvida. Na verdade o projeto já tem uma unit que trata de conexão via FDac e eu é que estava comendo bola. Só fiz a conexão e mudei a classe no botão de gerar [Lca.Orm.Comp.FireDac].
        Consegui gerar as Units satisfatóriamente! Falta algumas implementações que eu quero testar, mas ai é no Obj e não no gerador.
        Amanhã devo iniciar o desenvolvimento em SQLite (já que funcionou com o Firebird – usei apenas para fins didáticos porque não tenho trabalhado com ele), para criar meu BD de personalização, depois que conseguir vou tentar o SQL Server.
        Obrigado pela atenção e pelo compartilhamento do projeto!
        Se quiser, após a conclusão, posso disponibilizar estas novas units que pretendo desenvolver.
        Att,

        1. Que bom que tenha conseguido.

          Caso queira compartilhar seus fontes, faça pelo Git:
          https://github.com/luizsistemas/ORM-Basico-Delphi

          Com o tempo, andei fazendo algumas adaptações e acabei não atualizando. Vou ver se faço isso nos próximos dias, inclusive com um post sobre um “livebinding” (entre aspas mesmo!) meu, rsrsrs, para o projeto. Não tem nada a ver com o Livebinding do Delphi, mas um que fiz utilizando o padrão de projeto Observer.

          1. Bom dia! Que legal! Pelo que testei ontem não terei de “recriar” a roda, e sim aprender a usá-la… No projeto que fiz manualmente, criei atributos das tabelas no código fonte para quando associar com a tabela física eu saber exatamente o tamanho do campo correto (por exemplo limitar uma observação ou descrição automático) e também pretendo implementar máscaras para exibição, edição e impressão (como uso métodos diferentes para ambos, talvez eu simplifique para alguma forma que funcione nos três casos ou não, ainda não tentei isso), assim eu pretendo não precisar ficar editando os campos para usar as máscaras toda vez que mexer com os componentes.
            obrigado novamente!!! Bons códigos!

  4. Boa noite! Alguns dias sem conseguir testar, hoje eu finalizei a implementação com o SQL Server. Tive de mudar algumas units, e criei uma ADO genérica, porque o FDac não é gratuito para ele e eu não tenho disponibilidade de pagar no momento. Copiei e acertei 3 units e gerei a classe com sucesso! Agora vou tentar implementar as melhorias que havia pensado! Abs

  5. Luiz boa noite,
    Primeiramente, parabenizar você por este artigo que mudou meu jeito de programar, deixando o lado procedural e entrando no mundo do OOP.
    Mais agora me deparei com algumas dividas, que gostaria se for possível me ajudar.
    Sobre relacionamento de tabelas (FK) .
    Neste caso como poderia fazer para pegar a FK. e tambem fazer os relacionamentos de classes.
    Obrigado.

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.