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:

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;

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:

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

Insira uma nova classe chamada TDaoBase:

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

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:

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;

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:

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
 
...

Altere a classe TDaoUiB:

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.

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:

...
    function Inserir(ATabela: TTabela): Integer; override;
    function Salvar(ATabela: TTabela): Integer;  override;
    function Excluir(ATabela: TTabela): Integer; override;
    function Buscar(ATabela:TTabela): Integer; override;
...

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:

...
TRecParams = record
    Prop: TRttiProperty;
    Campo: string;
    Tabela: TTabela;
    Qry: TObject;
  end;
...

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:

...
    //configura parâmetros da query
    procedure ConfiguraParametro(AProp: TRttiProperty; ACampo: string;
      ATabela: TTabela; AQry: TObject; IsPK: Boolean = False); virtual;
...

E informe a query que será utilizado no record:

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

Ok! Agora, continuando na Base, insira o método que irá setar os dados em TTabela:

...
    //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);
...

Sua implementação é muito parecida com ConfiguraParametro:

...
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.
  • 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):

...
  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);
...

Agora, vamos atualizar TDaoUib com as alterações feitas na base:

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

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:

...
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;
...
  • 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:

...
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;
...
  • Utilizamos SetValue para alcançar o nosso objetivo.

Finalmente, implementamos o método Buscar:

...
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.

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:

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;

Execute a aplicação e teste:

  • Clique no botão excluir e depois em Buscar:
  • Clique em inserir e depois em Buscar:

Fontes

Como houve alteração na estrutura dos arquivos, sugiro que apague a versão anterior e somente depois descompacte esta nova versão.

Link Download

Chegamos ao fim de mais um artigo. Espero sinceramente que este trabalho seja útil à comunidade. Até a próxima!

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.

18 thoughts on “TBaseDao – Que tal um ORM Básico? Parte 10”

  1. 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+

  2. 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.

  3. 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.

  4. 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).

    1. 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.

  5. 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.

    1. 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.

      1. 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.

  6. 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.

    1. 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.

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.