Olá
Depois de alguns dias, bem corridos por sinal, estou de volta para mais um artigo.
Antes de mais nada…
Votação Encerrada!
No artigo anterior, abri uma votação para saber qual modelo deveríamos adotar para o nosso ORM: um baseado em Generics e outro em Interface.
A maioria escolheu Generics.
Apesar da pequena quantidade de votos (levando-se em consideração a grande quantidade de views que o artigo 9 teve), iremos seguir em frente.
O objetivo da votação foi colher novas ideias e sugestões para o nosso projeto. No fim, você escolhe qual modelo quer utilizar. O melhor será aquele que atender a sua necessidade. Para o nosso artigo, iremos utilizar o mais votado.
Um pequeno detalhe que passou despercebido
Olha, esperei uma avalanche de comentários quando criei o método ConfigParametro:
[sourcecode language=”delphi”]
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
if CompareText(AProp.PropertyType.Name, ‘TDateTime’) = 0 then
Params.ByNameAsDateTime[ACampo] := AProp.GetValue(ATabela).AsType<TDateTime>
else
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;
[/sourcecode]
Não houve comentários, mas tenho certeza que muitos se perguntaram:
Parâmetro “AQuery”, pra quê?
Resposta:
Se souber me diga, porque eu também quero saber! 😉
Não há necessidade deste parâmetro, visto que já temos um objeto do tipo TUIBQuery chamado Qry. Pelo menos não no estágio atual em que se encontra o projeto, mas isso irá mudar.
Vamos pensar um pouco…
Quando estivermos implementando uma nova classe de conexão (que nesta série será o IBX), teremos que copiar o método ConfigParametro para esta classe, e apesar de ter configurações específicas do IBX, muito código será repetido. E aí, quando necessitarmos alterar este método, teremos que correr todas as classes de conexões já implementadas para atualizá-lo.
Um dos princípio OO nos diz que “programe para interface e não para a implementação”. O termo interface aqui não significa que iremos herdar nossas classes estritamente e/ou diretamente de “IInterface“, mas também herdar de classes abstratas, que sirvam de base para as demais classes. Outro princípio é “encapsule o que varia”, ou seja, separe o que varia do que permanece igual. Desta forma, para o problema acima identificado, iremos utilizar herança , mantendo o que não muda na classe pai e colocar o que varia nas classes filhas.
Abra a unidade Base.pas:
[sourcecode language=”delphi”]
…
IDaoBase = interface
[‘{D06AAE8D-D5F7-47E7-BF11-E26687C11900}’]
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;
TDaoGenerico<T: IDaoBase> = class
private
FCon: T; // classe de conexao uib, ibx, …
procedure SetCon(const Value: T);
public
property Con: T read FCon write SetCon;
end;
…
[/sourcecode]
Insira uma nova classe chamada TDaoBase:
[sourcecode language=”delphi”]
…
TRecParams = record
Prop: TRttiProperty;
Campo: string;
Tabela: TTabela;
end;
IDaoBase = interface
[‘{D06AAE8D-D5F7-47E7-BF11-E26687C11900}’]
end;
TDaoBase = class(TInterfacedObject, IDaoBase)
protected
//métodos abstrados para os tipos de campos a serem utilizados nas classes filhas
procedure QryParamInteger(ARecParams: TRecParams); virtual; abstract;
procedure QryParamString(ARecParams: TRecParams); virtual; abstract;
procedure QryParamDate(ARecParams: TRecParams); virtual; abstract;
procedure QryParamCurrency(ARecParams: TRecParams); virtual; abstract;
procedure QryParamVariant(ARecParams: TRecParams); virtual; abstract;
function ExecutaQuery: Integer; virtual; abstract;
procedure FechaQuery; virtual; abstract;
procedure ConfiguraParametro(AProp: TRttiProperty; ACampo: string;
ATabela: TTabela; IsPK: Boolean = False); virtual;
public
function Inserir(ATabela: TTabela): Integer; virtual; abstract;
function Salvar(ATabela: TTabela): Integer; virtual; abstract;
function Excluir(ATabela: TTabela): Integer; virtual; abstract;
function InTransaction: Boolean; virtual; abstract;
procedure StartTransaction; virtual; abstract;
procedure Commit; virtual; abstract;
procedure RollBack; virtual; abstract;
end;
…
[/sourcecode]
Analisando o código:
- Transferimos as declarações dos métodos de IDaoBase para TDaoBase. Motivo: TDaoBase será o pai das classes de conexão e não mais IDaoBase (digamos que ele seja agora um avô); 🙂
- Linhas 15 a 19, temos os métodos que irão setar os parâmetros string, integer, currency, data, etc. Note que são métodos abstrados, ou seja, serão implementados nas classes filhas e não na classe pai. Note também, que ele usa um record como parâmetro, para facilitar a chamada;
- Linha 24, está aí o método ConfigParametro, apenas com uma alteração no nome: ConfiguraParametro. Perceba que ele tem um novo parâmetro: IsPK. Ele tem o objetivo de verificar se o campo que está sendo parametrizado é uma chave primária.
- Linhas 28 a 35, métodos que antes estavam em IDaoBase. Também abstratos.
Desta forma, por enquanto, o único método a ser implementado em TDaoBase é o ConfiguraParametro. Ctrl+Shift+C para implementá-lo:
[sourcecode language=”delphi”]
procedure TDaoBase.ConfiguraParametro(AProp: TRttiProperty; ACampo: string;
ATabela: TTabela; IsPK: Boolean);
var
Params: TRecParams;
begin
Params.Prop := AProp;
Params.Campo := ACampo;
Params.Tabela := ATabela;
case AProp.PropertyType.TypeKind of
tkInt64, tkInteger:
begin
if (IsPK) and (AProp.GetValue(ATabela).AsInteger = 0) then
raise Exception.Create(Format(‘Campo da Chave [%s] primária não informado!’,[ACampo]));
QryParamInteger(Params);
end;
tkChar, tkString, tkUString:
begin
if (IsPK) and (Trim(AProp.GetValue(ATabela).AsString) = ”) then
raise Exception.Create(Format(‘Campo da Chave [%s] primária não informado!’,[ACampo]));
QryParamString(Params);
end;
tkFloat:
begin
if (IsPK) and (AProp.GetValue(ATabela).AsVariant = 0) then
raise Exception.Create(Format(‘Campo da Chave [%s] primária não informado!’,[ACampo]));
if CompareText(AProp.PropertyType.Name, ‘TDateTime’) = 0 then
QryParamDate(Params)
else
QryParamCurrency(Params);
end;
tkVariant:
begin
QryParamVariant(Params);
end;
else
raise Exception.Create(‘Tipo de campo não conhecido: ‘ +
AProp.PropertyType.ToString);
end;
end;
[/sourcecode]
Analisando o código:
- Linhas 12,14,18,20 e 22, substituímos as chamadas diretas na query pelos métodos abstratos;
- Observe que o código agora conta com a verificação da chave primária, onde campos da PK são obrigatórios.
- Linha 27, não é comum ter campo data ou até mesmo currency na chave primária. Eu mesmo evito esta prática, mas se acontecer, estaremos tratando. Futuramente, poderemos fazer alguns testes para atestar o funcionamento deste trecho.
Abra DaoUib:
[sourcecode language=”delphi”]
unit DaoUib;
interface
uses Base, Rtti, Atributos, uib, system.SysUtils, System.Classes;
type
TDaoUib = class(TInterfacedObject, IDaoBase)
private
// conexao com o banco de dados
FDatabase: TUIBDataBase;
FTransaction: TUIBTransaction;
// Este método configura os parâmetros da AQuery.
procedure ConfigParametro(AQuery: TuibQuery; AProp: TRttiProperty; ACampo: string; ATabela: TTabela);
function ExecutaQuery: Integer;
procedure FechaQuery;
public
//query para execução dos comandos crud
Qry: TUIBQuery;
constructor Create(ADatabaseName: string);
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
…
[/sourcecode]
Altere a classe TDaoUiB:
[sourcecode language=”delphi”]
unit DaoUib;
interface
uses Base, Rtti, Atributos, uib, system.SysUtils, System.Classes;
type
TDaoUib = class(TDaoBase)
private
// conexao com o banco de dados
FDatabase: TUIBDataBase;
FTransaction: TUIBTransaction;
protected
// métodos responsáveis por setar os parâmetros
procedure QryParamInteger(ARecParams: TRecParams); override;
procedure QryParamString(ARecParams: TRecParams); override;
procedure QryParamDate(ARecParams: TRecParams); override;
procedure QryParamCurrency(ARecParams: TRecParams); override;
procedure QryParamVariant(ARecParams: TRecParams); override;
function ExecutaQuery: Integer; override;
procedure FechaQuery; override;
public
//query para execução dos comandos crud
Qry: TUIBQuery;
constructor Create(ADatabaseName: string);
function Inserir(ATabela: TTabela): Integer; override;
function Salvar(ATabela: TTabela): Integer; override;
function Excluir(ATabela: TTabela): Integer; override;
function InTransaction: Boolean; override;
procedure StartTransaction; override;
procedure Commit; override;
procedure RollBack; override;
end;
implementation
{ TDaoUib }
uses Vcl.forms, dialogs, System.TypInfo;
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;
procedure TDaoUib.QryParamCurrency(ARecParams: TRecParams);
begin
inherited;
with ARecParams do
begin
Qry.Params.ByNameAsCurrency[Campo] := Prop.GetValue(Tabela).AsCurrency;
end;
end;
procedure TDaoUib.QryParamDate(ARecParams: TRecParams);
begin
inherited;
with ARecParams do
begin
Qry.Params.ByNameAsDateTime[Campo] := Prop.GetValue(Tabela).AsType<TDateTime>;
end;
end;
procedure TDaoUib.QryParamInteger(ARecParams: TRecParams);
begin
inherited;
with ARecParams do
begin
Qry.Params.ByNameAsInteger[Campo] := Prop.GetValue(Tabela).AsInteger;
end;
end;
procedure TDaoUib.QryParamString(ARecParams: TRecParams);
begin
inherited;
with ARecParams do
begin
Qry.Params.ByNameAsString[Campo] := Prop.GetValue(Tabela).AsString;
end;
end;
procedure TDaoUib.QryParamVariant(ARecParams: TRecParams);
begin
inherited;
with ARecParams do
begin
Qry.Params.ByNameAsVariant[Campo] := Prop.GetValue(Tabela).AsVariant;
end;
end;
function TDaoUib.InTransaction: Boolean;
begin
Result := FTransaction.InTransaction;
end;
procedure TDaoUib.StartTransaction;
begin
FTransaction.StartTransaction;
end;
procedure TDaoUib.RollBack;
begin
FTransaction.RollBack;
end;
procedure TDaoUib.Commit;
begin
FTransaction.Commit;
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
ConfiguraParametro(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;
ConfiguraParametro(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;
ConfiguraParametro(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]
Analisando o código:
- Linha 8, TDaoUib descende agora de TDaoBase;
- Linhas 16 a 20, métodos a serem implementados da classe pai;
- Linhas 73 a 116, configuração dos parâmetros de acordo com o componente UIB.
- Os métodos Inserir, Excluir e Salvar foram alterados para utilizar o método ConfiguraParametro.
Resolvemos a questão da repetição do método ConfigParametro. Por hora, já é suficiente. Para os métodos CRUD eu penso em refatorá-los, mas vou deixar para depois.
Completando o CRUD
Vamos agora completar o CRUD. Para isso, falta apenas implementar o Read, ou seja, o método que irá buscar os dados no BD e setá-los no objeto do tipo TTabela. Em TDaoUib, insira o método Buscar:
[sourcecode language=”delphi”]
…
function Inserir(ATabela: TTabela): Integer; override;
function Salvar(ATabela: TTabela): Integer; override;
function Excluir(ATabela: TTabela): Integer; override;
function Buscar(ATabela:TTabela): Integer; override;
…
[/sourcecode]
Analisando o que deverá ser feito:
- Note que a assinatura dele segue o padrão dos demais.
- Poderemos utilizar o método Excluir como base para montar o código do Buscar, visto que este último também utilizará a chave primária para encontrar os dados.
- Ao encontrar os dados, precisaremos setá-los no objeto do tipo TTabela.
- Neste caso, para pegar os dados eu prefiro utilizar outro componente TUibQuery, para o caso de ser necessário utilizar uma transação diferente daquela que é utilizada para manipular os dados no BD. Por enquanto, iremos utilizar a mesma transação, mas poderemos mudar isso posteriormente.
- O método ConfiguraParametro pega os dados de TTabela e repassa para os parâmetros da query. Já no caso do Buscar, iremos necessitar fazer justamente o contrário, ou seja, pegar os dados da query e repassar para TTabela. Portanto, precisaremos de um novo método para isso. Consequentemente, também teremos métodos nas classes filhas para os variados tipos de campos.
- Como iremos utilizar um componente query diferente para manipulação e recuperação de dados, iremos mudar a assinatura do método ConfiguraParametro.
Abra Base.pas e altere TRecParams:
[sourcecode language=”delphi”]
…
TRecParams = record
Prop: TRttiProperty;
Campo: string;
Tabela: TTabela;
Qry: TObject;
end;
…
[/sourcecode]
Temos agora uma variável Qry do tipo TObject. E por que TObject e não TUibQuery? Simples! Lembre-se que estamos trabalhando a base. Não podemos acoplá-la a um componente específico de conexão.
Crie um novo parâmetro em ConfiguraParametro:
[sourcecode language=”delphi”]
…
//configura parâmetros da query
procedure ConfiguraParametro(AProp: TRttiProperty; ACampo: string;
ATabela: TTabela; AQry: TObject; IsPK: Boolean = False); virtual;
…
[/sourcecode]
E informe a query que será utilizado no record:
[sourcecode language=”delphi”]
…
procedure TDaoBase.ConfiguraParametro(AProp: TRttiProperty; ACampo: string;
ATabela: TTabela; AQry: TObject; IsPK: Boolean);
var
Params: TRecParams;
begin
Params.Prop := AProp;
Params.Campo := ACampo;
Params.Tabela := ATabela;
Params.Qry := AQry; // <— AQUI
case AProp.PropertyType.TypeKind of
tkInt64, tkInteger:
begin
if (IsPK) and (AProp.GetValue(ATabela).AsInteger = 0) then
raise Exception.Create(Format(‘Campo da Chave [%s] primária não informado!’,[ACampo]));
QryParamInteger(Params);
end;
tkChar, tkString, tkUString:
begin
if (IsPK) and (Trim(AProp.GetValue(ATabela).AsString) = ”) then
raise Exception.Create(Format(‘Campo da Chave [%s] primária não informado!’,[ACampo]));
QryParamString(Params);
end;
tkFloat:
begin
if (IsPK) and (AProp.GetValue(ATabela).AsVariant = 0) then
raise Exception.Create(Format(‘Campo da Chave [%s] primária não informado!’,[ACampo]));
if CompareText(AProp.PropertyType.Name, ‘TDateTime’) = 0 then
QryParamDate(Params)
else
QryParamCurrency(Params);
end;
tkVariant:
begin
QryParamVariant(Params);
end;
else
raise Exception.Create(‘Tipo de campo não conhecido: ‘ +
AProp.PropertyType.ToString);
end;
end;
…
[/sourcecode]
Ok! Agora, continuando na Base, insira o método que irá setar os dados em TTabela:
[sourcecode language=”delphi”]
…
//configura parâmetros da query
procedure ConfiguraParametro(AProp: TRttiProperty; ACampo: string;
ATabela: TTabela; AQry: TObject; IsPK: Boolean = False); virtual;
//seta os dados da query em TTabela
procedure SetaDadosTabela(AProp: TRttiProperty; ACampo: string;
ATabela: TTabela; AQry: TObject);
…
[/sourcecode]
Sua implementação é muito parecida com ConfiguraParametro:
[sourcecode language=”delphi”]
…
procedure TDaoBase.SetaDadosTabela(AProp: TRttiProperty; ACampo: string;
ATabela: TTabela; AQry: TObject);
var
Params: TRecParams;
begin
Params.Prop := AProp;
Params.Campo := ACampo;
Params.Tabela := ATabela;
Params.Qry := AQry;
case AProp.PropertyType.TypeKind of
tkInt64, tkInteger:
begin
SetaCamposInteger(Params);
end;
tkChar, tkString, tkUString:
begin
SetaCamposString(Params);
end;
tkFloat:
begin
if CompareText(AProp.PropertyType.Name, ‘TDateTime’) = 0 then
SetaCamposDate(Params)
else
SetaCamposCurrency(Params);
end;
else
raise Exception.Create(‘Tipo de campo não conhecido: ‘ +
AProp.PropertyType.ToString);
end;
end;
end.
[/sourcecode]
- Os métodos são muito parecidos realmente, mas como ficará na base, no momento não penso em algo diferente do que foi feito. Como sou averso à repetição, pode ser que eu altere estes códigos futuramente.
Veja que precisaremos declarar os métodos responsáveis por setar os dados (linhas 15,19,24 e 26). Faça isso (linhas 13 a 16):
[sourcecode language=”delphi”]
…
TDaoBase = class(TInterfacedObject, IDaoBase)
private
protected
//métodos abstrados para os tipos de campos a serem utilizados nas classes filhas
procedure QryParamInteger(ARecParams: TRecParams); virtual; abstract;
procedure QryParamString(ARecParams: TRecParams); virtual; abstract;
procedure QryParamDate(ARecParams: TRecParams); virtual; abstract;
procedure QryParamCurrency(ARecParams: TRecParams); virtual; abstract;
procedure QryParamVariant(ARecParams: TRecParams); virtual; abstract;
//métodos para setar os variados tipos de campos
procedure SetaCamposInteger(ARecParams: TRecParams); virtual; abstract;
procedure SetaCamposString(ARecParams: TRecParams); virtual; abstract;
procedure SetaCamposDate(ARecParams: TRecParams); virtual; abstract;
procedure SetaCamposCurrency(ARecParams: TRecParams); virtual; abstract;
function ExecutaQuery: Integer; virtual; abstract;
procedure FechaQuery; virtual; abstract;
//configura parâmetros da query
procedure ConfiguraParametro(AProp: TRttiProperty; ACampo: string;
ATabela: TTabela; AQry: TObject; IsPK: Boolean = False); virtual;
//seta os dados da query em TTabela
procedure SetaDadosTabela(AProp: TRttiProperty; ACampo: string;
ATabela: TTabela; AQry: TObject);
…
[/sourcecode]
Agora, vamos atualizar TDaoUib com as alterações feitas na base:
[sourcecode language=”delphi”]
…
type
TDaoUib = class(TDaoBase)
private
// conexao com o banco de dados
FDatabase: TUIBDataBase;
FTransaction: TUIBTransaction;
protected
// métodos responsáveis por setar os parâmetros
procedure QryParamInteger(ARecParams: TRecParams); override;
procedure QryParamString(ARecParams: TRecParams); override;
procedure QryParamDate(ARecParams: TRecParams); override;
procedure QryParamCurrency(ARecParams: TRecParams); override;
procedure QryParamVariant(ARecParams: TRecParams); override;
//métodos para setar os variados tipos de campos
procedure SetaCamposInteger(ARecParams: TRecParams); override;
procedure SetaCamposString(ARecParams: TRecParams); override;
procedure SetaCamposDate(ARecParams: TRecParams); override;
procedure SetaCamposCurrency(ARecParams: TRecParams); override;
function ExecutaQuery: Integer; override;
procedure FechaQuery; override;
public
//query para execução dos comandos crud
Qry: TUIBQuery;
constructor Create(ADatabaseName: string);
function Inserir(ATabela: TTabela): Integer; override;
function Salvar(ATabela: TTabela): Integer; override;
function Excluir(ATabela: TTabela): Integer; override;
function Buscar(ATabela:TTabela): Integer; override;
function InTransaction: Boolean; override;
procedure StartTransaction; override;
procedure Commit; override;
procedure RollBack; override;
end;
…
[/sourcecode]
Analisando o código:
- Linhas 17 a 20, declaração dos métodos que irão setar os variados tipos de campos;
- Linha 33, declaração do último método CRUD a ser implementado, o Buscar.
Segue os códigos dos métodos que configuram os parâmetros da query:
[sourcecode language=”delphi”]
…
procedure TDaoUib.QryParamCurrency(ARecParams: TRecParams);
begin
inherited;
with ARecParams do
begin
TUIBQuery(Qry).Params.ByNameAsCurrency[Campo] := Prop.GetValue(Tabela).AsCurrency;
end;
end;
procedure TDaoUib.QryParamDate(ARecParams: TRecParams);
begin
inherited;
with ARecParams do
begin
TUIBQuery(Qry).Params.ByNameAsDateTime[Campo] := Prop.GetValue(Tabela).AsType<TDateTime>;
end;
end;
procedure TDaoUib.QryParamInteger(ARecParams: TRecParams);
begin
inherited;
with ARecParams do
begin
TUIBQuery(Qry).Params.ByNameAsInteger[Campo] := Prop.GetValue(Tabela).AsInteger;
end;
end;
procedure TDaoUib.QryParamString(ARecParams: TRecParams);
begin
inherited;
with ARecParams do
begin
TUIBQuery(Qry).Params.ByNameAsString[Campo] := Prop.GetValue(Tabela).AsString;
end;
end;
procedure TDaoUib.QryParamVariant(ARecParams: TRecParams);
begin
inherited;
with ARecParams do
begin
TUIBQuery(Qry).Params.ByNameAsVariant[Campo] := Prop.GetValue(Tabela).AsVariant;
end;
end;
…
[/sourcecode]
- Fizemos um typecast, visto que a variável Qry do record é do tipo TObject.
Abaixo, os métodos que irão setar os dados da query em TTabela:
[sourcecode language=”delphi”]
…
procedure TDaoUib.SetaCamposCurrency(ARecParams: TRecParams);
begin
inherited;
with ARecParams do
begin
Prop.SetValue(Tabela, TUIBQuery(Qry).Fields.ByNameAsCurrency[Campo]);
end;
end;
procedure TDaoUib.SetaCamposDate(ARecParams: TRecParams);
begin
inherited;
with ARecParams do
begin
Prop.SetValue(Tabela, TUIBQuery(Qry).Fields.ByNameAsDateTime[Campo]);
end;
end;
procedure TDaoUib.SetaCamposInteger(ARecParams: TRecParams);
begin
inherited;
with ARecParams do
begin
Prop.SetValue(Tabela, TUIBQuery(Qry).Fields.ByNameAsInteger[Campo]);
end;
end;
procedure TDaoUib.SetaCamposString(ARecParams: TRecParams);
begin
inherited;
with ARecParams do
begin
Prop.SetValue(Tabela, TUIBQuery(Qry).Fields.ByNameAsString[Campo]);
end;
end;
…
[/sourcecode]
- Utilizamos SetValue para alcançar o nosso objetivo.
Finalmente, implementamos o método Buscar:
[sourcecode language=”delphi”]
…
function TDaoUib.Buscar(ATabela: TTabela): Integer;
var
Comando: TFuncReflexao;
Dados: TUIBQuery;
begin
Dados := TUIBQuery.Create(nil);
try
//crio uma variável do tipo TFuncReflexao – um método anônimo
Comando := function(ACampos: TCamposAnoni): Integer
var
Campo: string;
PropRtti: TRttiProperty;
begin
with Dados do
begin
Database := FDatabase;
Transaction := FTransaction;
sql.Add(‘select * 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
ConfiguraParametro(PropRtti, Campo, ATabela, Dados, True);
end;
end;
Open;
Result := Fields.RecordCount;
if Result > 0 then
begin
for PropRtti in ACampos.TipoRtti.GetProperties do
begin
Campo := PropRtti.Name;
SetaDadosTabela(PropRtti, Campo, ATabela, Dados);
ACampos.Sep := ‘,’;
end;
end;
end;
end;
//reflection da tabela e abertura da query preparada acima.
Result := ReflexaoSQL(ATabela, Comando);
finally
Dados.Free;
end;
end;
end.
[/sourcecode]
Analisando o código:
- Linha 7, criamos uma nova query: Dados.
- Linhas 17 a 20, configuramos a query e iniciamos a construção do select.
- Linha 32, setamos a chave primária. Note que passamos a query Dados e IsPK como True no parâmetro. ATENÇÃO: Lembre-se de alterar a chamada do ConfiguraParametro nos métodos Inserir, Salvar e Excluir, porém lá será o objeto Qry e não Dados. Em inserir e Salvar não é necessário informar o parâmetro IsPK.
- Linhas 35 e 35, abrimos a query e colhemos o result.
- Linhas 37 a 45, se encontrou os dados, inserimos no objeto do tipo TTabela através do método SetaDadosTabela.
- Linha 52, destruímos o objeto Dados.
Teste
Coloque um novo botão (Buscar) no formulário frmTesteAtributos:
Insira o seguinte código no botão Buscar:
[sourcecode language=”delphi”]
procedure TfrmTesteAtributos.btnBuscarClick(Sender: TObject);
var
ATab: TTeste;
Registros: Integer;
begin
ATab := TTeste.Create;
try
ATab.Id := 1;
Registros := dmPrin.Dao.Con.Buscar(ATab);
if Registros>0 then
begin
Memo1.Lines.Add(Format(‘Registro encontrado: %d’, [Registros]));
Memo1.Lines.Add(Format(‘UF……..: %s’ , [ATab.Estado]));
Memo1.Lines.Add(Format(‘Descrição.: %s’ , [ATab.Descricao]));
Memo1.Lines.Add(Format(‘Data……: %s’ , [DateTimeToStr(ATab.Data)]));
Memo1.Lines.Add(Format(‘Habitantes: %d’ , [ATab.Habitantes]));
Memo1.Lines.Add(Format(‘Renda…..: %m’ , [ATab.RendaPerCapta]));
end
else
ShowMessage(‘Registro não encontrado!’);
finally
ATab.Free;
end;
end;
[/sourcecode]
Execute a aplicação e teste:
Fontes
Como houve alteração na estrutura dos arquivos, sugiro que apague a versão anterior e somente depois descompacte esta nova versão.
Chegamos ao fim de mais um artigo. Espero sinceramente que este trabalho seja útil à comunidade. Até a próxima!
Pessoal
Essa ainda não é forma final da nossa classe TDaoUib. No próximo artigo, iremos analisar tudo o que foi feito e se for necessário faremos as devidas alterações.
Portanto, críticas até são bem-vindas, mas saiba que ainda não é algo que podemos chamar de pronto.
Beleza? T+
Parabéns pela excelente qualidade dos artigos publicados.
Obrigado Filipe. Já estou trabalhando no próximo!
Estou trabalhando numa estrutura um pouco semelhante para a Universidade onde trabalho. Só que aqui estou usando DBExpress com DB2.
Olá Luiz,
É besteira minha, mas no método de Buscar a mensagem ficou como Registro Excluído (o texto que aparece no memo).
Muito bom o artigo, parabéns. Agora ficarei na espera dos próximos.
Obrigado pela iniciativa, estou aprendendo bastante, assim que possível irei aplicar essa técnica na empresa onde trabalho, vai facilitar bastante.
Sim, eu já tinha percebido isso. Como é algo que não irá afetar o entendimento, deixei para corrigir no próximo artigo.
Olá Luiz,
Gostaria de parabenizar voce pelo excelente conteúdo do artigo e mais ainda pela qualidade da didática.
Comecei a ler ontem e já estou ansioso pelo próximo, mais uma vez parabéns.
Olá Paulo
Muito obrigado! Em breve estarei enviando mais um artigo… agora tive que dar um tempinho devido a montanha de coisas que estava se formando aqui. 🙂
Caro Luiz Carlos,
Estou acompanhando a evolução do Framework e estou muito curioso para saber se o mesmo terá capacidade de mapear relacionamento entre classes (Chaves Estrangeiras) e como será a interação com os objetos de manipulação dos dados (Form, Edit, etc).
Muitos já me perguntaram sobre este recurso. Bom, o objetivo inicial creio que já conseguimos alcançar que foi nos livrarmos dos intermináveis inserts, updates e deletes (que ficará mais claro vislumbrar a partir do próximo artigo), pois este é um ORM Básico, não é mesmo?
Agora, dependendo do rumo que esta série tomar no futuro, iremos sim analisar esta solicitação.
Boa Noite Luiz Carlos.
Eu estou acompanhando seu ensinamento e verifiquei que tem um problema no buscar se eu tiver mais de um campo com PKs ele não consegue me retornar valores, mas se tiver uma unica chave funciona certinho. Poderia me explicar o porque deste motivo.
ATT: Volnei
OBS: Meus parabéns pelo ótimo material de estudo, continua assim, que a comunidade de programadores agradece.
Volnei, obrigado por acessar o blog.
Ainda é necessário alguns ajustes e correções. Estou tirando alguns dias para descansar. Foi um ano de muito trabalho.
Quando eu voltar, irei continuar com a série, onde irei implementar o Ibx, aproveitando para analisar o erro que você citou.
Obrigado pela atenção.
Desculpe se eu tirei do descanso não era minha intenção era só pra informar.
Sei como é a vida de programador .
Feliz Natal e um Próspero Ano Novo cheio de realizações.
Muito obrigado, e sei material está sendo te grande valia pra mim.
rsrss Realmente, vida de programador é uma batalha… mas sempre que posso dou passadinha aqui. 🙂
Feliz Natal e Próspero Ano Novo a você e a todos que acompanham este blog.
Olá Luiz,
É uma pena que não tenha dado mais continuidade ao mini-curso, eu fiz um projeto de DAO para mim, no qual fiz algumas implementação mediante seu ensinamento e ficou bem legal, eu não conseguia visualizar como seria trabalhar com OO dessa forma em Delphi, mas isso facilitou demais a minha vida, agora as alterações são bem mais fáceis de fazer em comparação com o modo antigo (que eu achava que era fácil).
Muito obrigado e até a próxima aula.
Olá Ricardo, fico feliz que os artigos tenham de certa forma lhe ajudado.
Com a virada do ano, muitos projetos surgiram e exigiram de mim atenção acima do previsto. Mas não quero deixar os artigos de mão. Pretendo voltar a escrevê-los em breve.
Link quebrado do arquivo, se poder renovar agradeço.
Ok, corrigido.