Qual a maneira correta de alterar um formulario mestre/detal
10/06/2008
0
Só que a tabela detalhe eu tenho que trazer todos os itens que ja foram gravados ai tem os botões inserir - alterar - excluir - gravar - cancelar
esta alteração fica dentro do mesmo form que tambem pode ser alterado que é a tabela mestre.
Ele vem no form na seguinte maneira, tem alguns tabsheets aonde primeiro abro para alterar algo na tabela mestre e depois no tabsheet seguinte tem os dados da tabela detalhe. Só que esta alteração na tabela detalhe tem que ser feita antes de gravar na tabela mestre, ai o problema, preciso gravar tipo temporario e depois no final quando for gravar na tabela mestre atualizar a tabela detalhe tambem. Pois se o usuario resolver no final cancelar tudo a tabela detalhe tem que excluir tambem as atualizações que foram feitas.
Se eu deixar para aplicar o applyupdates da tabela detalhe somente na hora de gravar os dados alterados da tabela mestre vem somente a ultima inclusão por exemplo que eu fiz na tabela detalhe, e se no caso tiver varias inclusões ai elas se perdem, por isso pensei gravar num dataset temporario e depois passar tudo de uma só vez no final. Mais quando fui fazer assim apareçeu um erro estranho:
[color=red:3b8ec4f249]´Espaço insuficiente de armazenamento para concluir a operação´[/color:3b8ec4f249] Este erro vem na segunda inclusão na tabela temporaria detalhe.
Então gostaria de saber qual é o procedimento correto de fazer este tipo de alteração em dias tabelas relacionadas?
Grato
Adriano
Adriano_servitec
Posts
10/06/2008
Adriano_servitec
10/06/2008
Adriano_servitec
10/06/2008
Brunodsr
O Enio teve uma dúvida parecida com a sua em outro tópico. Para trabalhar com master detail, vc tem que colocar as duas tabelas na mesma transação.
1. Start transaction;
2. Insert na tabela pai;
3. Insert na tabela filho;
4. Commit ou rollback.
O mesmo se aplica para alterar e excluir. Todas as alterações/inserções e deleções são executadas no banco apenas após o commit ou rollback.
10/06/2008
Adriano_servitec
Estava pensando em fazer algum tipo de campo de ´Status´ gravar no banco e se o usuario cancelar a operação aplicar uma trigger desfazendo os dados que estão com o ´Status´ em alguma situação diferente que a definitiva na tabela filho. Mais não sei se vai ficar complicado ou se vai firar um negocio dificil de controla depois.
Agora se tiver como commitar somente no final ficaria mais facil, mais não sei fazer isso em multcamadas.
10/06/2008
Brunodsr
Eu fiz uma aplicação assim:
Fachada (telas)
Controle (regras de negocio - TCliente, TPedidoCompra, TFornecedor)
Dados (Comandos SQL e conexao ao banco)
Para saber a ação a ser tomada eu sobrecarreguei o construtor do form, passando uma outra classe de parametros. Ex.:
Param := TParametros.Create(self); Param.Action := AcAlterar; Param.State := NoState; ... ... ... Form := TFrPedidoCompra.Create(Application, Param);
Sempre que a ação era alterar, eu carregava os ítens lançados anteriormente, inserindo-os em um dataset (apenas em memória) e deixava ele disponível para novas inserções, modificações e deleções.
No momento em que eu confirmava a operação, enviada o próprio CDS do cabeçalho e o CDS dos ítens, que seriam usados pela minha classe de controle para pegar as modificações e jogar p/ o banco. Eu acesso as modificações pelas propriedades data e delta do CDS.
Dá uma olhada no help do delphi sobre o data e delta. Tem uns exemplos lá mostrando como se faz isso. Se não conseguir, me dá um toque.
Espero ter ajudado.
10/06/2008
Adriano_servitec
Desculpe não ter entendido, mais valeu pela força ai.
11/06/2008
Adriano_servitec
{:Grava os dados no banco} dm.ConInternetLocal.Open; try //começo do trecho try/finnaly if dsAltDEAR.DataSet.State in [dsEdit, dsInsert] then try //começo do trecho try/except dsAltDEAR.DataSet.Post; (dsAltDEAR.DataSet as TClientDataSet).ApplyUpdates(0); {:atualiza a tabela filho} with cdsAltTB_Proprietario do begin Close; CommandText := ´ UPDATE TB_PROPRIETARIO ´+ ´ SET STATUS = :pSTATUS ´+ ´ WHERE (ID_chave = :pID_chave) ´+ ´ and (STATUS = ´´N´´) ´+ ´ and (STATUS = ´´E´´) ´; if FieldByName(´STATUS´).AsString = ´N´ then Params.ParamByName(´pSTATUS´).AsString := ´G´ else if FieldByName(´STATUS´).AsString = ´E´ then Params.ParamByName(´pSTATUS´).AsString := ´F´; Params.ParamByName(´pid_chave´).AsString := maskAltContDear.Text; Execute; {:Select para atualizar a grid} Close; CommandText := ´ select * from TB_PROPRIETARIO ´+ ´ where ID_chave = :pID_chave ´; Params.ParamByName(´pid_chave´).AsString := maskAltContDear.Text; Open; end; Dc_MessageDlg(´Dados gravados com sucesso´, mtInformation, [mbOk],0); {:atualiza os dados da tabela altDear} dsAltDEAR.DataSet.Close; dsAltDEAR.DataSet.Open; except on E:Exception do begin dc_MessageDlg(´Ocorreu um erro neste processo. ´ +#1313 +´Erro gerado: ´+13+ E.Message + 1313 + ´Todo o processo foi abortado!´, mtError, [mbOk],0); Abort; dsAltDEAR.DataSet.Cancel; end; end; //Final try/except finally dm.ConInternetLocal.Close; end; //final do trecho try/finally
Aonde fiz um [b:f382c01c9b]debug[/b:f382c01c9b] linha a linha pra ver como estava o procedimento e percebi que o código pula este IF aqui
if FieldByName(´STATUS´).AsString = ´N´ then Params.ParamByName(´pSTATUS´).AsString := ´G´ else if FieldByName(´STATUS´).AsString = ´E´ then Params.ParamByName(´pSTATUS´).AsString := ´F´;
Ou seja pelo que estou tentando fazer é: Se no banco tiver dados gravados com o STATUS N mudar para STATUS G e se tiver dados gravados com STATUS E mudar para STATUS F no update.
Então na hora de debugar ele entra no 1º IF e pula para o 2º IF que também pula para próxima linha ignorando o que está lá no banco, que tem dados com o STATUS N e E também.
Alguma idéia pessoal?
Grato
Adriano
11/06/2008
Adriano_servitec
begin //inherited; {:Grava os dados no banco} dm.ConInternetLocal.Open; try //começo do trecho try/finnaly if dsAltDEAR.DataSet.State in [dsEdit, dsInsert] then try //começo do trecho try/except dsAltDEAR.DataSet.Post; (dsAltDEAR.DataSet as TClientDataSet).ApplyUpdates(0); {:atualiza a tabela filho} with cdsAltTB_Proprietario do begin Close; CommandText := ´ UPDATE TB_PROPRIETARIO ´+ ´ SET STATUS = :pSTATUS ´+ ´ WHERE (ID_chave = :pID_chave) ´+ ´ and (STATUS = ´´N´´) ´+ ´ or (STATUS = ´´E´´) ´; {:Traz o resultado do Status numa tabela Auxiliar} with qryAuxiliarTbPropr do begin Close; SQL.Clear; SQL.Text := ´ select STATUS from TB_PROPRIETARIO ´+ ´ where STATUS = ´´N´´ or STATUS = ´´E´´ ´; Open; DisableControls; {;fechas os controles para ir mais rápido} First; {:Primeiro registro} while not Eof do {:enquanto não for o fim, altera os parametros} begin if FieldByName(´STATUS´).AsString = ´N´ then cdsAltTB_Proprietario.Params.ParamByName(´pSTATUS´).AsString := ´G´ else if FieldByName(´STATUS´).AsString = ´E´ then cdsAltTB_Proprietario.Params.ParamByName(´pSTATUS´).AsString := ´F´; Next; {:próximo registro} end; EnableControls; {:abre novamente os controles} end; Params.ParamByName(´pid_chave´).AsString := maskAltContDear.Text; Execute; {:Select para atualizar a grid} Close; CommandText := ´ select * from TB_PROPRIETARIO ´+ ´ where ID_chave = :pID_chave ´; Params.ParamByName(´pid_chave´).AsString := maskAltContDear.Text; Open; end; Dc_MessageDlg(´Dados gravados com sucesso´, mtInformation, [mbOk],0); {:atualiza os dados da tabela altDear} dsAltDEAR.DataSet.Close; dsAltDEAR.DataSet.Open; except on E:Exception do begin dc_MessageDlg(´Ocorreu um erro neste processo. ´ +1313 +´Erro gerado: ´+13+ E.Message + 1313 + ´Todo o processo foi abortado!´, mtError, [mbOk],0); Abort; dsAltDEAR.DataSet.Cancel; end; end; //Final try/except finally dm.ConInternetLocal.Close; end; //final do trecho try/finally
ai adptei este trecho no código
{:Traz o resultado do Status numa tabela Auxiliar} with qryAuxiliarTbPropr do begin Close; SQL.Clear; SQL.Text := ´ select STATUS from TB_PROPRIETARIO ´+ ´ where STATUS = ´´N´´ or STATUS = ´´E´´ ´; Open; DisableControls; {;fechas os controles para ir mais rápido} First; {:Primeiro registro} while not Eof do {:enquanto não for o fim, altera os parametros} begin if FieldByName(´STATUS´).AsString = ´N´ then cdsAltTB_Proprietario.Params.ParamByName(´pSTATUS´).AsString := ´G´ else if FieldByName(´STATUS´).AsString = ´E´ then cdsAltTB_Proprietario.Params.ParamByName(´pSTATUS´).AsString := ´F´; Next; {:próximo registro} end; EnableControls; {:abre novamente os controles} end;
12/06/2008
Adriano_servitec
Tratando por um campo é complicado e dificil, e não é 100¬ funcional, alem de um extenso código sem necessidade e depois de difícil manutenção.
A unica coisa que eu preciso é poder incluir, deletar e alterar na tabela filho sem passar para o banco ou se gravar no banco e na tabela pai que esta em modo de edição eu cancelar tudo na tabela filho retornar ao que estava antes gravado no banco.
Se eu deixo pra aplicar o applyupdates (tabe Filho) no final aponta para o erro ´Update Affected mode than 1 record´ se eu mudo o datasetprovider a propriedade Options->poAllowMultRecordUpdates = True grava, mais grava tudo repetido o que foi feito por ultimo na tabela filho.
Alguma outra idéia pessoal?
12/06/2008
Adriano_servitec
Tabela mestre é esta aqui
CREATE TABLE DEAR ( ID_DEAR INTEGER NOT NULL, GERAR_CHAVE VARCHAR(12) NOT NULL, NOM_SEGUR VARCHAR(60) NOT NULL, SEXO CHAR(1), APELIDO VARCHAR(30), DT_NASCIMENTO DATE NOT NULL, RG_SEGUR VARCHAR(35), CPF_SEGUR VARCHAR(14) NOT NULL, EST_CIVIL VARCHAR(30), END_SEGUR VARCHAR(65), BAIRRO VARCHAR(50), MINICIPIO VARCHAR(50), UF CHAR(2), TE_SEGUR VARCHAR(30), CTPS_SEGUR VARCHAR(25), PONTO_REF VARCHAR(35), CONFRONTANTES BLOB SUB_TYPE 1 SEGMENT SIZE 80, MATRICULA VARCHAR(10), DATA_MATRICULA DATE, PROF_ATUAL VARCHAR(40), CAT_TRAB VARCHAR(40), REG_INDIVIDUAL CHAR(1), REG_ECON_FAMILIAR CHAR(1), TIPO_CULTIVO BLOB SUB_TYPE 1 SEGMENT SIZE 80, PROD_CULTIVADOS BLOB SUB_TYPE 1 SEGMENT SIZE 80, ANEXO BLOB SUB_TYPE 1 SEGMENT SIZE 80, TESTEMUNHA_1 VARCHAR(60), TESTEMUNHA_2 VARCHAR(60), RG_TEST_1 VARCHAR(35), RG_TEST_2 VARCHAR(35), END_TEST_1 VARCHAR(50), END_TEST_2 VARCHAR(50), USUARIO VARCHAR(40), DATA_EFETIVO TIMESTAMP, AUDITORIA BLOB SUB_TYPE 1 SEGMENT SIZE 80, CPF_TEST_1 VARCHAR(14), CPF_TEST_2 VARCHAR(14), STATUS CHAR(1) );
Na table Master
/******************************************************************************/ /**** Primary Keys ****/ /******************************************************************************/ ALTER TABLE DEAR ADD CONSTRAINT PK_DEAR PRIMARY KEY (GERAR_CHAVE);
Tabela detalhe é esta aqui
CREATE TABLE TB_PROPRIETARIO ( ID_PROPR INTEGER NOT NULL, NOM_PROPR VARCHAR(60), END_PROPR VARCHAR(60), DATA_INI DATE, DATA_FIM DATE, CAT_TRABALHADOR VARCHAR(60), ID_CHAVE VARCHAR(12) NOT NULL, STATUS CHAR(1) );
Na table detail
/******************************************************************************/ /**** Primary Keys ****/ /******************************************************************************/ ALTER TABLE TB_PROPRIETARIO ADD CONSTRAINT PK_TB_PROPRIETARIO PRIMARY KEY (ID_PROPR); /******************************************************************************/ /**** Foreign Keys ****/ /******************************************************************************/ ALTER TABLE TB_PROPRIETARIO ADD CONSTRAINT FK_TB_PROPRIETARIO_1 FOREIGN KEY (ID_CHAVE) REFERENCES DEAR (GERAR_CHAVE) ON DELETE CASCADE ON UPDATE CASCADE;
Fiz no onexit do edit assim para chamar a busca master/detail
procedure TfrmAltDEAR.maskAltContDearExit(Sender: TObject); begin inherited; with cdsAltDEAR do //TABLE MASTER begin Close; Params[0].AsString := maskAltContDear.Text; Open; if not IsEmpty then begin maskAltContDear.Text := Params[0].AsString; with cdsAltTB_Proprietario do //TABLE DETAIL begin {:Select para atualizar a grid} Close; Params[1].AsString := maskAltContDear.Text; Open; end; {:muda o datasource para modo de edit} if cdsAltDEAR.State <> dsEdit then cdsAltDEAR.Edit; end else begin Dc_MessageDlg(´Nº do Controle: ´+maskAltContDear.Text+´ não encontrada.´+13+ ´Digite outro Nº do Controle no formato 9999/AAAA Ex:(0001/2008).´, mtInformation, [MbOk],0); maskAltContDear.SetFocus; end; end; end;
Então após isso no onexit ele vai me trazer os dados da tabela mestre e detalhe também, no segundo tabsheet tenho alguns dbeditds que fazem parte da tabela detalhe aonde vai poder incluir/deletar e alterar os dados, mas ainda não terminei de atualizar na tabela mestre, então no final tem mais alguns buttons para atualizar o que estou fazendo tambem na table mestre e se no caso o usuario resolver Cancelar tudo, o que deve ser feito é retornar como estava antes no banco nas duas tabelas.
Estou usando DBExpress+SQLDataSet+DSProvider (Camada Servidor) e na camada Cliente estou usando SOAPConnection ou COMConnection que esta ligado no ConnectionBroker que faz ligações com os ClientDataSet´s
Eu renomeei o ConnectionBroker para ConInternetLocal
Estou tambem usando Firebird 2.0
Eu tinha resolvido de tirar o botão insert neste formulário e deixar apenas gravar, alterar e excluir
Mais se eu excluo sem aplicar o applyupdate ele exclui e novamente ele retorna
procedure TfrmAltDEAR.btnExcluirClick(Sender: TObject); begin // inherited; if Dc_MessageDlg(´Deseja realmente excluir o registro,´+#13+´do DEAR - ´+ dsAltTB_Proprietario.DataSet.FieldByName(´Nom_Propr´).AsString+´?´,mtInformation, [mbYes,mbNo],0) = mrYes then begin if not dsAltTB_Proprietario.DataSet.Active then dsAltTB_Proprietario.DataSet.Open; try dsAltTB_Proprietario.DataSet.Delete; {:Passa para o banco os dados que estão em cache na tabela AltTab_Proprietario} //(dsAltTB_Proprietario.DataSet as TClientDataSet).ApplyUpdates(0); Dc_MessageDlg(´Dados excluídos com sucesso´, mtInformation, [mbOk],0); {:Atualiza a tabela AltTab_Proprietario} dsAltTB_Proprietario.DataSet.Close; dsAltTB_Proprietario.DataSet.Open; dsAltTB_Proprietario.DataSet.Refresh; {:Trata os botões no no modulo Propriedade} btnExcluir.Enabled := True; btnAlterar.Enabled := True; btnGravar.Enabled := False; btnCancelar.Enabled := False; except on E:Exception do begin dc_MessageDlg(´Ocorreu um erro neste processo. ´ +1313 +´Erro gerado: ´+13+ E.Message + #1313 + ´Todo o processo foi abortado!´, mtError, [mbOk],0); Abort; dsAltTB_Proprietario.DataSet.Cancel; end; end; //Final try/except end; end;
Se eu aplico agora o applyupdate ele me mostra a mensagem de erro
[b:fb2e99ac58]´Update Affected mode than 1 record´[/b:fb2e99ac58] Se eu mudar no DSProvider para poAllowRecorsUpdates(Não me recordo o nome de cabeça, mais deve ser este mesmo) para TRUE salva sem erro, mais todos ficma igual ao ultimo que foi feiot alteração. Então tambem não funcionou.
Se eu colocar o applyupdates da tabela detail no AfterScroll da table master tambem da erro no onexit daquele edit que eu uso para mostrar os dados das tabelas master/detail. Ou seja bem no select do detail.
Então esta parte eu não consegui resolver, mais como disse na parte do Insert resolvi fazer um novo form só para Insert e usar uma tabela Temporaria para cadastrar os dados detail e só gravar no final. Assim se o usuario resolver abandonar a operação se desfaz caso contrário grava na tabela.
{:Grava os dados no banco} dm.ConInternetLocal.Open; try {:começo do trecho try/finally} if DSPadrao.DataSet.State in [dsEdit, dsInsert] then try {:começo do trecho try/except do bloco da tabela DEAR} if Label36.Caption = ´00000´ then raise Exception.Create(´Atenção! Não foi gerado o controle do DEAR´+13+ ´Não vai ser gravado os dados.´) else DSPadrao.DataSet.FieldByName(´gerar_chave´).asString := Label36.Caption; DSPadrao.DataSet.Post; {:Grava na tabela DEAR os dados do ClientDataSet} (DSPadrao.DataSet as TClientDataSet).ApplyUpdates(0); DSPadrao.DataSet.Close; {:atualiza os dados.} DSPadrao.DataSet.Open; {Grava na tabela Proprietários} Bookmark := cdsPropTemp.GetBookmark; {:Ponteiro - alocar memória e atribuir um valor} cdsPropTemp.DisableControls; {:Desativar exibição de registros de dados que controla} try {:Abre o Bloco Try/Except ou seja Tente ou abre uma exceção} try {::Abre o Bloco Try/Finnaly ou seja Tente ou Finalmente} cdsPropTemp.First; {:Ir para o primeiro registro na tabela temporaria} while not cdsPropTemp.Eof do //Enquanto não for fim na tabela temporaria transfere begin {:Transferindo os dados para o banco} if not dm.cdsSPProp_DEAR.Active = True then dm.cdsSPProp_DEAR.Active := True; dm.cdsSPProp_DEAR.Append; dm.cdsSPProp_DEARNOM_PROPR.AsString := cdsPropTempNomProp.AsString; dm.cdsSPProp_DEAREND_PROPR.AsString := cdsPropTempEndProp.AsString; dm.cdsSPProp_DEARDATA_INI.Value := cdsPropTempDataINI.Value; dm.cdsSPProp_DEARDATA_FIM.Value := cdsPropTempDataFim.Value; dm.cdsSPProp_DEARCAT_TRABALHADOR.AsString := cdsPropTempCatTrab.AsString; dm.cdsSPProp_DEARID_CHAVE.AsString := cdsPropTempid_chave.AsString; dm.cdsSPProp_DEAR.Post; cdsPropTemp.Next; {:Proximo dado da tabela temporaria} end; {:final do loop} finally {:Finalmente} cdsPropTemp.GotoBookmark(Bookmark); cdsPropTemp.EnableControls; {:Abre a exibição de registros de dados dos controles, se faz necessário ao final} cdsPropTemp.FreeBookmark(Bookmark);{:Limpa da memoria o ponteiro bookmark} if dm.IBTransaction1.Active = False then dm.IBTransaction1.Active := True; dm.IBTransaction1.Commit; {:Limpa toda tabela que esta em memoria} (dsPropTemp.DataSet as TClientDataSet).CancelUpdates; {:Fecha todos Botões} btnInsere.Enabled := False; btnExcluir.Enabled := False; btnAlterar.Enabled := False; btnGravar.Enabled := False; btnCancelar.Enabled := False; b_first.Enabled := False; b_prior.Enabled := False; b_next.Enabled := False; b_last.Enabled := False; end; {:Final do bloco try/finally da tabela proprietario} except raise Exception.Create(´Problemas na transação dos dados, o processo não foi incluído no banco.´); Exit; end; {:Final do bloco try/except da tabela proprietário} Dc_MessageDlg(´Dados gravados com sucesso´, mtInformation, [mbOk],0); except on E:Exception do begin dc_MessageDlg(´Ocorreu um erro neste processo. ´ +#1313 +´Erro gerado: ´+13+ E.Message + 1313 + ´Todo o processo foi abortado!´, mtError, [mbOk],0); Exit; DSPadrao.DataSet.Cancel; dm.IBTransaction1.Rollback; end; end; {:Final try/except da tabela DEAR} finally dm.ConInternetLocal.Close; end; {:final do trecho try/finally}
Clique aqui para fazer login e interagir na Comunidade :)