Fórum DBXReader para ClientDataSet e alterar #429692

25/11/2012

0

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

Responder

Posts

25/11/2012

Claudia Nogueira

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

Gostei + 0

26/11/2012

Rafael Costa

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

Gostei + 0

26/11/2012

Claudia Nogueira

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

Gostei + 0

26/11/2012

Rodolpho Silva

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.

Responder

Gostei + 0

26/11/2012

Marco Salles

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;


Responder

Gostei + 0

26/11/2012

Rafael Costa

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

Gostei + 0

26/11/2012

Rodolpho Silva

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]
Responder

Gostei + 0

26/11/2012

Marco Salles

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
Responder

Gostei + 0

26/11/2012

Rafael Costa

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

Gostei + 0

26/11/2012

Rafael Costa

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

Gostei + 0

27/11/2012

Rafael Costa

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

Gostei + 0

27/11/2012

Marco Salles

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

Responder

Gostei + 0

28/11/2012

Rafael Costa

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
Responder

Gostei + 0

28/11/2012

Marco Salles

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

Responder

Gostei + 0

28/11/2012

Rafael Costa

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

Gostei + 0

Utilizamos cookies para fornecer uma melhor experiência para nossos usuários, consulte nossa política de privacidade.

Aceitar