DBXReader para ClientDataSet e alterar

Delphi

25/11/2012

Olá pessoal, estou tentando fazer uma aplicação aqui que se tudo der certo irá se comunicar tanto com uma aplicação cliente desktop como com um app para Android. Para isso estou desenvolvendo minhas classes de manipulação de dados no servidor usando DBXReader. Portanto criei uma classe onde tenho o método alterar que recebe como parametro um DBXReader que conterá o registro que foi alterado pelo cliente.
O problema é que quando jogo esse DBXreader para um CDS e realizo um applyUpdates, ao invez de executar um Update ele tenta realizar um insert o que ocasiona um erro de PK pois ela ja existe no banco.
Segue meu codigo para que possam entender melhor:

procedure TDaoServerClass.pprAlterar(ipObj: TDBXReader);
begin
    TDBXClientDataSetReader.CopyReaderToClientDataSet(ipObj, FCds);
    FCds.Edit;
    FCds.Post;
    FCds.ApplyUpdates(0);
end;
//FCds é um CDS que esta no meu DataModule o qual já esta corretamente ligado a um Provider->TSQLDataSet->TSQLConncetion.


Alguem saberia me dizer se tem alguma maneira de forçar o CDS a realizar um update ao invez de um insert?
Rafael Costa

Rafael Costa

Curtidas 0

Respostas

Claudia Nogueira

Claudia Nogueira

25/11/2012

Sabe o que pode ser também, o FCds não estar posicionado no registro que você está alterando, aí por exemplo você está tentando alterar um que a chave primária = 2, hora que você chama pra editar o FCds se posiciona em outro registro, provavelmente o primeiro, aí como você está mandando o código 2 e ele está posicionado no 1 e não no 2 aí dá o erro da FK porque ele está tentando mudar o código do 1 pra 2 e o 2 já existe.
Com esse erro da FK está dando a impressão que está inserindo, mas na verdade está editando mesmo.

Quer ver, faz um teste aí, hora de chamar a função pra alterar não passa o field do código. Aí provavelmente não vai dar o erro, mas vai editar outro registro.
GOSTEI 0
Rafael Costa

Rafael Costa

25/11/2012

Olá Claudiadnh, primeiramente obrigado por responder, mas este não é o caso pois eu garanto que esse DBXReader só possui um registro quando eu chamo a função no meu cliente. E também para ter certeza eu já verifiquei o recordcount dele e é sempre um.

E a operação que esta sendo realizada é um insert mesmo pois eu coloquei um breakpoint no metodo BeforeUpdateRecord do meu provider e o parametro UpdateKind esta setado para ukInsert.
GOSTEI 0
Claudia Nogueira

Claudia Nogueira

25/11/2012

Hum entendi, então realmente não é o que eu pensava.
Vou tentar ver mais alguma coisa aqui, enquanto isso vamos ver se mais alguém responde.
GOSTEI 0
Rodolpho Silva

Rodolpho Silva

25/11/2012

Colega Rafael,

Esta função:
TDBXClientDataSetReader.CopyReaderToClientDataSet(ipObj, FCds);

Está fazendo uma cópia dos dados do DBXReader ao Cds logo, isso é via "insert". Dessa maneira,
este registro fica marcado no Delta do Cds como "novo" então, no momento do ApplyUpdate o DataProvider
realizará um insert na base de dados. E mesmo que você faça isso:
FCds.Edit;
FCds.Post;
FCds.ApplyUpdates(0);

Não invalida a definição de "insert" do registro para o DataProvider. Pelo que entendi, você só conseguirá resolver o problema de 2 maneiras:
1) Abrir o seu "FCds" com o registro em questão e atualizar conforme sua regra de negócios
2) Passar um Cds como parâmetro (e não mais DBXReader) onde este contém os dados à serem atualizados. Para fazer a cópia do seu Cds de
parâmetro ao FCds, basta fazer isso:
FCds.Data := CdsParametro.Data;

Pois são copiados todos os registros e seus respectivos estados (update, delete, etc...)

Bem, espero ter ajudado.

GOSTEI 0
Marco Salles

Marco Salles

25/11/2012

Do modo que vc esta fazendo , vc esta carregando o cds com o CopyReaderToClientDataSet e isto não passa pelo provider
Neste momento vc esta carregando o Cds com os dados
quando vc instrue o cds em memória para editar ele ira editar
quando vc instrue o Cds para salvar ele ira salva
Porém quando vc instrue o Cds para dar um applayUpdates o provider ira resolver
ele tentara aplicar todos os dados que estão em memória

Vc pode ver isto atraves do Xml gerado pelo Open Close ou CopyReaderToClientDataSet ... Os dois Xml indicam o que
alteração , edição ou exclusão.. Esses Xml que é trafegado e resolvido pelo provider

Tb vc pode utilizar o ChangeCount para ver esta alteração

compare


procedure TDaoServerClass.pprAlterar(ipObj: TDBXReader);
begin
TDBXClientDataSetReader.CopyReaderToClientDataSet(ipObj, FCds);
Showmessage(inttostr(Fcds.ChangeCount));  //Vc espera que vc Zero mas não é
//FCds.Edit;
//FCds.Post;
//FCds.ApplyUpdates(0);
end;


procedure TDaoServerClass.pprAlterar(ipObj: TDBXReader);
begin
Fcds.close;
Fcds.open;
Showmessage(inttostr(Fcds.ChangeCount));  //Vc espera que vc Zero e È ...
//FCds.Edit;
//FCds.Post;
//FCds.ApplyUpdates(0);
end;


GOSTEI 0
Rafael Costa

Rafael Costa

25/11/2012

Então, a opção de passar um ClientDataSet não é viável, pois irei precisar chamar essa função de um app do Android e até onde eu já vi nas classes exportadas pelo Delphi para realizar a comunicação com o Android não existe o ClientDataSet.

Quanto a outra solução eu já tinha imaginado que poderia dar certo mas não queria utiliza-la, pois terei que novamente realizar uma busca no banco, tudo bem que agora será pelo Id da tabela e o meu server esta na mesma maquina do banco, mas ainda sim será uma outra busca.

mas se não tiver outra solução vai ser essa mesmo.
Obrigado a todos pela ajuda.
GOSTEI 0
Rodolpho Silva

Rodolpho Silva

25/11/2012

Então, a opção de passar um ClientDataSet não é viável, pois irei precisar chamar essa função de um app do Android e até onde eu já vi nas classes exportadas pelo Delphi para realizar a comunicação com o Android não existe o ClientDataSet.

Quanto a outra solução eu já tinha imaginado que poderia dar certo mas não queria utiliza-la, pois terei que novamente realizar uma busca no banco, tudo bem que agora será pelo Id da tabela e o meu server esta na mesma maquina do banco, mas ainda sim será uma outra busca.

mas se não tiver outra solução vai ser essa mesmo.
Obrigado a todos pela ajuda.


Entendi. Por isso é importante o uso de uma framework ORM (desenvolvida por nós mesmo ou de terceiros) para lidarmos melhor com situações como estas.

Segue algumas frameworks ORM: [url]http://code.google.com/p/delphi-orm/[/url] e [url]http://tiopf.sourceforge.net/index.shtml[/url]
GOSTEI 0
Marco Salles

Marco Salles

25/11/2012

se ue pudesse editar ... desculpe o instrue repetido tres vezes no post anterior . È que eu escrevo sem ler e muito rápido

Realmente o framework ORM muito interressante . Porém uma outra saida é zerar este log de alterações

basta chmar método MergeChangeLog apos vc carrega-lo veja


procedure TDaoServerClass.pprAlterar(ipObj: TDBXReader);
begin
TDBXClientDataSetReader.CopyReaderToClientDataSet(ipObj, FCds);
Fcds.MergeChangeLog;  //*****************************************************
FCds.Edit;
FCds.Post;
FCds.ApplyUpdates(0);
end;
//FCds é um CDS que esta no meu DataModule o qual já esta corretamente ligado a um Provider->TSQLDataSet->TSQLConncetion.


Perceba , eu não estou dizendo que esta é a melhor solução , so coloquei o pq do erro obtido por vc
GOSTEI 0
Rafael Costa

Rafael Costa

25/11/2012

Realmente framework ORM é uma boa opção, eu uso bastante quando estou desenvolvendo em Java.
Mas quando estou no Delphi me sinto como se estivesse deixando de usufruir das vantagens dos componentes do Delphi (CDS, Provider... etc), pois teria que criar meus Models e atribuir campo a campo.
Ex.:
meuObj.nome := EditNome.text;
meuObj.idade := EditIdade.text

enquanto usando CDS eu apenas ligo os meus DBEdits e pronto. Por isso eu tentando fazer essa classe, pois minha ideia era continuar usando o CDS no cliente porem no momento de gravar ao invez de utilizar o applyUpdate direto do CDS, eu o transformaria em um DBXReader e o passaria para minha classe no server que faria o processo inverso e então realizaria a gravação no banco, não necessitando assim a escrita de comandos SQL para insert, update ou delete.

Vou tentar a solução proposta pelo Marco, se der certo post o resultado aqui.
Valeu..
GOSTEI 0
Rafael Costa

Rafael Costa

25/11/2012

E complementando, usando o DBXReader eu ainda conseguiria me comunicar com o meu server através de um app Android, IOS, etc.
GOSTEI 0
Rafael Costa

Rafael Costa

25/11/2012

É isso ai pessoal consegui fazer usando o Fcds.MergeChangeCount, apesar que tiver que fazer um código um tanto dúvidoso.. rsrs, mas enfim, ainda acho melhor do que fazer a busca novamente no banco.
Segue o código para caso alguem precise:
procedure TDaoServerClass.pprAlterar(ipObj: TDBXReader);
var
  vaCds: TClientDataSet;
  I: Integer;
begin
  vaCds := TClientDataSet.Create(nil);
  try
    TDBXClientDataSetReader.CopyReaderToClientDataSet(ipObj, FCds);
    vaCds.Data := FCds.Data;
    // vou alterar todos os registros agora. Tenho que fazer isso pois senao na hora de fazer o applyUpdates nada ira ocorrer
    for I := 0 to FCds.FieldCount - 1 do
      begin
        if not (pfInKey in (FCds.Fields[i].ProviderFlags)) then
          begin
            FCds.Edit;
            FCds.Fields[I].Clear;
            FCds.Post;
          end;
      end;
    // zerando o ChangeCount do CDS
    FCds.MergeChangeLog;
    // voltando os valores originais. Fazendo isso, faço o CDS achar que seus valores foram alterados, realizando um update ao inves do insert no momento do apply.
    for I := 0 to FCds.FieldCount - 1 do
      begin
        FCds.Edit;
        FCds.Fields[I] := vaCds.Fields[I];
        FCds.Post;
      end;
    // gravando no banco
    FCds.ApplyUpdates(0);
  finally
    vaCds.Free;
  end;


Valeu pessoal. Problema resolvido.
GOSTEI 0
Marco Salles

Marco Salles

25/11/2012

Bem Rafael , desculpe mas vc parece não ter entendido o Objetivo do método MergeChangelong

Tb não da para entender o pq que vc Aplica no banco algo que vc ja tem

È so carregar com o

TDBXClientDataSetReader.CopyReaderToClientDataSet(ipObj, FCds);

da um Fcds.MergeChangelong e toda alteração que vc fizer posteriore será considerada um Updade e não Um Insert

Desculpe mas esta muito estranho tudo isto

[]sds

GOSTEI 0
Rafael Costa

Rafael Costa

25/11/2012

Olá Marco, acho que você não entendeu qual a ideia da minha procedure.
é o seguinte, esse DBXReader que é passado como parametro já esta com as informações alteradas, o que eu preciso é gravar elas no banco. Se eu faço um mergeChangeLog e logo apos faço um applyUpdates, nada será gravado no banco, pois para o CDS nada foi alterado.
Por isso faço essa volta ai. Salvo os valores originais em outro CDS, depois limpo todos os campos do Fcds e então faço o MergeChangeLog. E então, percorro ele novamente e volto os valores originais e faço o applyUpdates que agora sim irá gerar os sqls para Update e não Insert.

Pode ser que tenha outra forma mais simples de fazer isso, mas não consegui encontrá-la.

Flw
GOSTEI 0
Marco Salles

Marco Salles

25/11/2012

Quem faz alteração é o ExecuteUpdate e não ha retorno de DbXreader

Utilizar o ExecuteQuery retorna DbXreader , porem em Edit o retorno é Nil

qual a isntrução que vc esta utilizando para fazer esta alteração

Normalamente é assim



var
   FConnetion:TDBXConnection;
   cmd: TDBXCommand;
begin
   Fconnetion:=TDBXConnectionFactory.GetConnectionFactory.GetConnection
        ('COnexaoValida','SYSDBA','masterkey');
try
   cmd:=Fconnetion.CreateCommand;
    try    
     cmd.Text := 'UPDATE '+Table+ ' SET '+Fields+'='+QuotedStr('Alterar')+' Where Country = '+QuotedStr('Condicao');
      Result := cmd.ExecuteQuery;  //Retorno é Nil eu utilizo o cmd.ExecuteUpdate para comandos de Atualização
    except
      raise;
    end;
finally
 Fconnetion.free;
end;
end;


Postei algo que eu utilizo para Editar dados com TDBXCommand , vc esta utilizando algo parecido ?
Ou vc esta editando e retornando com o select Campso From Tabela o ReaderDbx ???

desculpe mas queria entender , apesar de lhe aconselhar o MergeChangeLong ainda esta obscuro o que vc esta fazendo

[]sds

GOSTEI 0
Rafael Costa

Rafael Costa

25/11/2012

Não entendi bem o que você quis dizer, mas não uso esse TDBXCommand em lugar algum (Na verdade uso, mas somente nas buscas e não em updates ou inserts). Quem efetua a gravação no banco é o CDS->Provider->TSQLDataset. Vou tentar explicar melhor como todo o processo funciona.
Estou desenvolvendo uma aplicação multicamada, onde terei uma aplicação server, uma aplicação cliente Desktop e uma aplicação cliente Mobile(Android). No meu server tenho a classe TDaoServerClass que é uma classe abstrata para as minhas outras classes como por exemplo TDaoUsuarioServer. Essa TDaoServerClass irá conter algumas funções basicas que todas as classes que herdarem deram irão conter tbm as quais são: operações CRUD, sendo que a busca pode ser por ID ou todas.
TDaoServerClass = class abstract(TPersistent)
  strict private
    procedure ppvCommit(ipCds: TClientDataSet);
  strict protected
    FNomeTabela: string;
    FDataSet: TSQLDataSet;
    FProvider: TDataSetProvider;
    FCds: TClientDataSet;
    FCommand: TDBXCommand;

    function fprSalvar(ipObj: TDBXReader): Integer; virtual;
    procedure pprAlterar(ipObj: TDBXReader); virtual;
    procedure pprExcluir(ipId: Integer); virtual; abstract;
    function fprBuscar(ipId: Integer): TDBXReader; virtual;
    function fprBuscarTodos: TDBXReader; virtual;
...
//restante do codigo omitido


No momento estou desenvolvendo somente a aplicação Server e Cliente desktop. então na minha aplicação cliente faço uma conexao datasnap normal (CDS->DSProviderConnection). Porém ao clicar no botão buscar por exemplo eu faço uma chamada a minha função no server. ex.:
var
    vaDao:TDaoUsuarioServerClient;
    vaReader:TDBXReader;
begin
  DataModule.ClientDataSetUsuario.open;//nao traz nada pq no server eu coloquei no SQL where codigo =0;
  vaDao := TDaoUsuarioServerClient.Create(ClientModule1.SQLConnection1.DBXConnection);
  vaReader := vaDao.fprBuscarTodos;
  TDBXClientDataSetReader.CopyReaderToClientDataSet(vaReader, DataModule.ClientDataSetUsuario);//carrego o Cds
end;


e no momento de alterar/salvar/deletar faço algo semelhante. ex.:
procedure TForm1.btnAlterarClick(Sender: TObject);
var
  vaUser: TDaoUsuarioServerClient;
  vaReader: TDBXReader;
begin
  try
    DataModule.ClientDataSetUsuario.Post;
    vaUser := TDaoUsuarioServerClient.Create(DataModule.SQLConnection1.DBXConnection);
    vaReader := TDBXDataSetReader.Create(DataModule.ClientDataSetUsuario);
    vaUser.pprAlterar(vaReader);
  except
    on e: Exception do
      ShowMessage(e.Message);

  end;
end;


Essa foi uma maneira que encontrei (ainda não sei se vai funcionar em 100% dos casos) de não precisar escrever SQLs para INSERT/UPDATE e DElete e também garantir que quando minha aplicação mobile estiver pronta ela somente irá precisar criar um DBXReader e chamar as funções do meu server.

É isso ai, espero ter explicado melhor desta vez.
flw.
GOSTEI 0
Marco Salles

Marco Salles

25/11/2012

Bem , coloquei o código acima que é uma das formas de carregar um TDBXReader . Um outro modo de carregar um TDBXReader é
utilizar um comando do tipo TDBXDataSetReader.Create(ClientDataSet, False (* InstanceOwner *) ).
Quando vc faz vaReader := vaDao.fprBuscarTodos vc esta carregando um TDBXReader ... A minha dúvida foi exatamente esta
como vc esta carregando este DbxRerader ???
GOSTEI 0
Rafael Costa

Rafael Costa

25/11/2012

Sim, fprBuscarTodos é uma função no meu server que me retorna um TDBXReader com todos os registros.
GOSTEI 0
Marco Salles

Marco Salles

25/11/2012

Sim, fprBuscarTodos é uma função no meu server que me retorna um TDBXReader com todos os registros.


Sim , mas qual o código que que vc faz isto .. Eu li passei dois modos , por acaso vc esta utilizando um terceiro ?
somemte a instrução de carregar o DbxReader que eu estou comentando ... Pode passar ou é algo confidencial ?

[]sds
GOSTEI 0
Rafael Costa

Rafael Costa

25/11/2012

Descuple, não tinha entendido o que você estava querendo. Estou usando o Command, semelhante ao que você mostrou ai,
mas utilizo ele somente nos meus métodos de busca.
Segue o código das minhas duas function padrão de busca

function TDaoServerClass.fprBuscar(ipId: Integer): TDBXReader;
var
  vaSql: string;
begin
  //FDataSet é um TSQLDataSet que se encontra no meu DataModule, nele eu já coloquei o meu SQL. ex. select * from usuario where codigo = 0;
  vaSql := TSQLGenerator.removeFilters(FDataSet.commandText);//retiro qualquer clausula where do sql
  vaSql := TSQLGenerator.filterInteger(FNomeTabela, coID, ipId, vaSql);//adiciono uma clausula where filtrando pelo ID
  FCommand.Text := vaSql;
  Result := FCommand.ExecuteQuery;
end;

function TDaoServerClass.fprBuscarTodos: TDBXReader;
var
  vaSql: string;
begin
  vaSql := TSQLGenerator.removeFilters(FDataSet.commandText);
  FCommand.Text := vaSql;
  Result := FCommand.ExecuteQuery;
end;
GOSTEI 0
POSTAR