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:

[sourcecode language=”delphi”]
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;
[/sourcecode]

Agora iremos alterar os comandos SQL:
[sourcecode language=”delphi”]
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;
[/sourcecode]

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:
[sourcecode language=”delphi”]
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;
[/sourcecode]

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:
[sourcecode language=”delphi”]
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.
[/sourcecode]

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!

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:

      [sourcecode language=”delphi”]
      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;
      [/sourcecode]

      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 *