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!
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.
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.
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?
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.
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 ?
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:
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.
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 ?
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.
Certamente.
Agradeço sempre Luiz.
Abraços.