Fórum DBXReader para ClientDataSet e alterar #429692
25/11/2012
0
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
Curtir tópico
+ 0Posts
25/11/2012
Claudia Nogueira
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
26/11/2012
Rafael Costa
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
26/11/2012
Claudia Nogueira
Vou tentar ver mais alguma coisa aqui, enquanto isso vamos ver se mais alguém responde.
Gostei + 0
26/11/2012
Rodolpho Silva
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
26/11/2012
Marco Salles
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
26/11/2012
Rafael Costa
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
26/11/2012
Rodolpho Silva
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
26/11/2012
Marco Salles
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
26/11/2012
Rafael Costa
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
26/11/2012
Rafael Costa
Gostei + 0
27/11/2012
Rafael Costa
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
27/11/2012
Marco Salles
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
28/11/2012
Rafael Costa
é 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
28/11/2012
Marco Salles
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
28/11/2012
Rafael Costa
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
Clique aqui para fazer login e interagir na Comunidade :)