dbExpress Interbase 6.5 Delphi 7.0
Uso os componetes
SQLConnection1 -> SQLDataSet1 -> DataSetProvider1 -> ClientDataSet1 -> DataSource1
ligados conforme manda as boas normas
Tenho tb um DbGrid , um Data Sorce e um Botao
Apresnto os dados faço algumas alteraçoes e quando clico no botão
procedure TForm1.Button1Click(Sender: TObject);
begin
ClientDataSet1.ApplyUpdates(0);
end;
[b:09ef8812a7]me dá o erro abaixo :[/b:09ef8812a7]
[URL=http://imageshack.us][img:09ef8812a7]http://img85.imageshack.us/img85/6292/imagemdbespress19wc.png[/img:09ef8812a7][/URL]
[b:09ef8812a7]e seguindo com a execução do aplicativo , me da o erro[/b:09ef8812a7]
[URL=http://imageshack.us][img:09ef8812a7]http://img116.imageshack.us/img116/8593/imagemdbespress21lo.png[/img:09ef8812a7][/URL]
o problema é que qualquer tabela que vou abrir no Interbase , e depois tento salvar alguns dados <usando a tecnologia DbExpress> , me da este erro....
Usando o IBExpert consigo salvar normalmente as modificaçoes...
Alem disso ja desistalei o Delphi 7.0 e tb o Interbase 6.5 ... E nada
espero muito ajuda . Obtrigado...
Marco Salles
Respostas
Vinicius2k
17/03/2006
Verifique a instrução SQL do TSQLDataSet.
Com dbExpress, os nomes dos objetos do banco de dados (tabelas, campos, SPs, etc) devem estar em maiúsculas. Ex:
// erro select campo1, campo2, campo3 from tabela // correto select CAMPO1, CAMPO2, CAMPO3 from TABELA
Marco Salles
17/03/2006
é um exemplo do livro do Guinther , da pag14 do seu livro (Programação para banco de dados)
Para se ter uma idéia da simplicidade o SQL esta Assim
SELECT *FROM CUSTOMER
é o exemplo dele do próprio guinther
isto esta me dando danda dor de cabeça , que ja desistalei e reistalei tudo que tinha direito na máquina... E O pior , tinha componentes de terceiros que vou ter o trabalhão de reistala-los
Levei o problema para casa e la tb deu o mesmo erro
o erro acontece , quando eu simplesmente edito qualquer campo e dou um ClientDataSet1.ApplyUpdates(*); *=0 ou -1
verifiquei devido a segunda mensagem que Fala de Chave Primária.. Algo esta reclamando da ausencia desta Chave.. Mas em processo de edição , não teria sentido ele reclamar Ou teria ????
Outra coisa , criei uma Banco de Dados no Interbase , e nele uma tabela muito simples com dois campos apenas
A Dll desta tabela e esta assim
CREATE TABLE TOTENTANDO ( COD_VENDA INTEGER NOT NULL, NOME_ALUNO VARCHAR(30) );
eu então atraves do DbGrid insiro os dados , tomando o cuidado para não ter nenhum registro repetido e logo no primeiro dado quando clico no botão ele da a mensagem de erro escrita la em cima no Primeiro post..
Porem , quando se tem um Banco com Generator e Triger (Que é o caso do CUSTOMER) eu consegui inserir e entrar com os Dados , so que tive que excluir o Campo ´PK´ do Sql , deixando o Banco retornar o Valor para esta Chave...
[b:9cfb56af5d]Mas qy=uando não se tem Generator e ne Triger , como no caso da tabela que criei la em cima , porque eu não esto uconseguindo digitar dados , Mesmo quando insiro todos os valores
corretamente para os Campos (Cod_Venda e Nome_Aluno)[/b:9cfb56af5d]
O Sql neste ultimo Caso tb é Muito simples
SELECT *FROM TOTENTANDO
Onde TOTENTANDO É O nome do minha tabela
[b:9cfb56af5d]Conclusão : So posso incluir algum valor se a chave primaria tiver um Generator.. Não tem sentido isso.. MAs é assim que esta funcionando comigo... As tabelas que eu tenho Chave Primaria com GENERATOR eu uso a query sem definir esse campo e incluo os outros dados no DBGRID , ai não da erro... Mas isto não é muito lógico ou é [/b:9cfb56af5d]
Vinicius2k
17/03/2006
O exemplo que você está seguindo pede que você altere os valores padrão de alguma das propriedades do TDataSetProvider?
Se sim, quais?
Não creio que seja um problema relacionado a existir ou não Generator + Trigger e sim existir ou não uma chave primária:
Se sua tabela não tem uma PK e o [b:1d1b6c2286]UpdateMode[/b:1d1b6c2286] do TDataSetProvieder estiver como [b:1d1b6c2286]upWhereKeyOnly[/b:1d1b6c2286], ele não possui uma chave para utilizar no Where, logo, não consegue atualizar...
Não posso afirmar que seja isso... É só um exemplo de como as configurações do TDataSetProvider influenciam no tratamento da informação.
Adriano Santos
17/03/2006
Cara estou com o Vinicius2K, acredito que não tenha nada a ver com o fato de ter ou não PK na tabela. Acredito que seja alguma configuração de propriedade mesmo.
Vc tem como me passar o exemplinho que está tendo dificuldade? Queria testar este final de semana. Não sou nenhum expert em DBExpress, mas talvez consiga algo.
flw.
Marco Salles
17/03/2006
Então vamos lá
A estrutura que estou usando [color=darkblue:aed0e1e635][b:aed0e1e635]Poxa , acho que todos que puderem testar e comentar o seu resultado , seria otimo..
O Exemplinho é muito fácil e rápido
Não toma nen um minuto.. Mas me ajuda demais[/b:aed0e1e635][/color:aed0e1e635]
A estrutura que to usando é
[color=darkblue:aed0e1e635][b:aed0e1e635]Ta ai ... Rodo a aplicação e façao aqualquer alteração no DbGrid e Clico no Botão... [/b:aed0e1e635][/color:aed0e1e635]
:?: :?: :?: :?: :?: :?: :?:
Faço simplesmente isso e não da certo :cry: :cry: :cry: O que acontece no caso de voce :?: :?: :?:
Por favor , postem os resultados.....
Vinicius2k
17/03/2006
Só para me certificar que foi apenas um erro de digitação...
A instrução SQL que você informa está incorreta: falta um espaço entre o ´*´ e o ´FROM´.
Marco Salles
17/03/2006
Não , foi erro mesmo .... Não me atinei para isto.. Nunca , nen na epoca do BDE
Parece que esta resolsolvido o problema.... Mas porque da esta diferença tão grande ????Nunca me atinei isto , nen usando o BDE , nunca deu falta desse Espaço .... Agora eu ja não sei se eu colocava ou colcava naturalmente , mas usando BDE isto para mim nunca foi problema
Marco Salles
17/03/2006
Sobre esse asunto :
Nomeu caso que estou estudando a[b:464ffd2c75] tabela employye [/b:464ffd2c75]temos uma chave cujo select esta assim:
Select * From CUSTOMER
a biografia manda , manda efetuar os seguintes passos
Com essas medidas , que estão descritas no livro do Guinther pag 15
estamos fazendo uma configuração na clausula WHERE , para ser otimizada.... Isto é importante para processar a instrução de maneira muito mais rápida
:cry: :cry: :cry: :cry:
[color=darkblue:464ffd2c75][b:464ffd2c75]Na hora de executar a aplicação , da erro.. Não consigo atualizar (ClintDataSet.ApplyUpdates(0));[/b:464ffd2c75][/color:464ffd2c75]
[b:464ffd2c75]Alguem ou o proprio Vinicus , consegue enxergar algo nesse processo que não estou fazendo ou deixando de Fazer...[/b:464ffd2c75]
[b:464ffd2c75]O Elefante esta pesado[/b:464ffd2c75]
Se quiserem testar , é importante porque esta tecnologia é a que estará em ´Alfa´ durante os proximos cinco anos.. Temos que começar
Muito obrigado
Martins
17/03/2006
E testei, deu tudo certo aqui, sem mensagem de erro nem nada, quer q eu mande o exemplo para vc ver? Se quiser é só me passar o e-mail, não disponibilizo para mais pessoas pq não tenho um server para hospedar o código.
Abraço!
Marco Salles
17/03/2006
Se não corrigir este erro , futuramente outras pessaos irão errar também
Eu tenho 99¬ de certeza que esta errado isto aqui
[b:800e7c8393]Voce pode ter os mesmo fields no ClinteDataSert , mas é necessário que se tenha esses Tfilds no SqlQuery.. Pois bem , esta fo ia conclusão que eu cheguei lendo pesquisando e experimentando[/b:800e7c8393]
Martins
17/03/2006
E não tive problemas, vc quer q eu te mande o exemplo para vc dá uma olhada, ressalto ainda q o mesmo foi feito em Delphi 6, q é a única versão q tenho nessa máquina.
Valew!!!!
Marco Salles
17/03/2006
Porque teoricamente quem ´´e responsável por enviar as instruçoes Sqk para o DataSetProvider é nesse caso O SqlQuery e não o clientDataSet
Se voce consegui realizar isto somente definindo Tfields para o ClinteDataSet Meu email ´´e este :
salhamoda@uol.com.br
Martins
17/03/2006
Porque teoricamente quem ´´e responsável por enviar as instruçoes Sqk para o DataSetProvider é nesse caso O SqlQuery e não o clientDataSet
Se voce consegui realizar isto somente definindo Tfields para o ClinteDataSet Meu email ´´e este :
salhamoda@uol.com.br[/quote:d0b4b188fb]
Enviei para seu e-mail, e confirmei aquele ant-spam chato da UOL, hehehe :P
Tá, qualquer coisa, posta aqui.
Valew
Vinicius2k
17/03/2006
No modelo proposto, ´quem manda´ nas configurações de TFields é o DataSet (TSQLDataSet/TSQLQuery) e não o TClientDataSet. [b:1e298542e1]As alterações na propriedade ProviderFlags devem ser feitas nos TFields do TSQLQuery/TSQLDataSet[/b:1e298542e1] e você nem precisa de TFields no TClientDataSet para isso (logicamente pode precisar para outras coisas).
1. Adicione todos os TFields no TSQLQuery/TSQLDataSet.
2. Faça as mudanças necessárias ao seu propósito.
3. Então, se quiser, adicione os TFields no TClientDataSet. Você verá que os TFields do TClientDataSet terão herdadas as alterações feitas nos TFields do TSQLDataSet/TSQLQuery.
Correndo o risco de parecer pretencioso por se tratar de um livro do Guinther, se ele não o orienta desta forma, infelizmente, ele está errado.
Marco Salles
17/03/2006
Se voce perceber nos meus tópicos anteriores , eu bati o tempo todo nesta tecla.. E O Martins bateu em uma outra direção
Gentilmente , ele me enviou um email com o seu projeto e pude perceber uma diferença entre o que fiz e o que ele fez
No seu exemplo o Martins usou Um [b:945a9e01fb]SqlDataSet[/b:945a9e01fb] em contraMão do [b:945a9e01fb]SqlQuery[/b:945a9e01fb] que estou usando...
De posse disso , para verificar a [b:945a9e01fb]otimização da Instrução Sql[/b:945a9e01fb]([color=darkred:945a9e01fb]Atraves dos ProvinderFlgas)[/color:945a9e01fb] , coloquei Um [color=darkblue:945a9e01fb]SqlMonitor [/color:945a9e01fb] no Projeto e o configurei para me fornecer um [b:945a9e01fb]Arquivo De Log[/b:945a9e01fb]
[color=darkblue:945a9e01fb][b:945a9e01fb]Nesse artigo de Log pude constatar que o Sql esta mesmo OTIMIZADO , sem que se configurrasse os TFILEDS do TSQLDataSet[/b:945a9e01fb][/color:945a9e01fb]
:!: :!: :!: :!:
De posse desta informação ,[b:945a9e01fb] retirei [/b:945a9e01fb]também os TFileds do ClinteDatset e refis a execução , para que un[b:945a9e01fb] Novo [/b:945a9e01fb]Arquivo de log fosse gerado...
:!: :!: :!:
Para minha surpresa , o Arquivo de log gerado também esta[b:945a9e01fb] OTIMIZADO[/b:945a9e01fb]
:idea: :idea: :idea:
[color=darkblue:945a9e01fb][b:945a9e01fb]ORA , sera uma propriedade do TSQLDataSet , que nos bastidores , ja faz esta otimização .... [/b:945a9e01fb][/color:945a9e01fb] :arrow: Não tenho nenhuma biografia a repeito desse comentário, mas fica registrado que [b:945a9e01fb]foi isso que o Arquivo Log me mostrou[/b:945a9e01fb] , quando usei um SQLDataSet , sem fazer nenhuma configuração dos ProviderFlags dos seus DataSets
Veja parte que interresa do Arquivo gerado , usando SqlDataset sem Configurar o ProviderFlags
O mesmo arquivo Gerado usando SQLQuery , sem confighurar Os ProviderFlags
No mais
O livro esta assim sim,
se eu antes de comprar o livro li os vinte artigos do clube delphi sobre dbexpress/DataSnap to na página 15 com esta dificuldade imagine quando passar para um artigo aonde eu não manjar nada , ai que não vou sair do lugar :cry: :cry: :cry: :cry:
Martins
17/03/2006
Abraço!
Martins
17/03/2006
Alterei o ProviderFlags apenas nos TFields do TClientDataSet.
Alterei o ProviderFlags no TFields do TSQLQuery apenas
Utilizando TSQLDataSet sem configurar ProviderFlags.
Usando TSQLQuery sem configurar ProviderFlags
Estou :?
Qual a melhor config. para se obter o melhor desempenho usando DbExpress :?:
Vinicius2k
17/03/2006
Não existe um modelo ´correto´ de configuração das ProviderFlags dos TFields e do UpdateMode do TDataSetProvider. Tudo depende do caso. Exemplo:
Em um ambiente multi-usuário o usuário A traz o registro X para alteração. No mesmo momento o usuário B também traz o mesmo registro X para alteração.
- Com UpdateMode em upWhereKeyOnly, quem aplicar os updates depois irá sobrepor as alterações do anterior. Isto porque você, provavelmente, definiu pfInKey para o TField da chave primária e, normalmente, ela não é alterada. Prevalecerá a última alteração.
- Com UpdateMode em upWhereAll, quem aplicar os updates depois não conseguirá sobrepor as alterações feitas pelo primeiro usuário. Isto porque na segunda aplicação dos updates, todos os campos já não mais possuem o mesmo valor anterior.
É você quem deve definir isso pois podem existir casos aonde upWhereAll é necessário.
Com relação a seus testes Martins, não consigo comprová-los. Até aonde sei, quaisquer alterações nas ProviderFlags devem ser feitas no DataSet e mesmo usando o TSQLDataSet sem configurações nas ProviderFlags irá resultar em uma instrução com todos os campos no WHERE, logicamente, dependendo de como está configurado o UpdateMode do TDataSetProvider.
Marco Salles
17/03/2006
Sinceramente , acho dificil entender esta situação...... Estranho pensar que fazer ou não uma alteração em um determinado registro , esta alteração , sera ou não sera aplicada , dependendo de ser antes ou depois de alguem... No meu conceito e entendimento , qualquer alteração tem que ser validada no momento em que ela é aplicada .. Se outro usuário aplicar outras alteraçoes posteriores que prevaleçam as deles , que são ,mais recentes...
Quando eu me referi sobre o teste que realizei usando o SqlDataSet sem quiasquer alteraçoes no ProviderFlags dos fileds do SqlDataSet , eu coloquei a propriedade UpdateMode do DataSetProvider configurado para
upWhereAll...
:?: :?: :?: :?: :?:
Mas agora voce me deixou em dúvida [b:b004886f8d]vinicius[/b:b004886f8d] , do jeito que voce falou , seria necessário ou Alterar os Flags [color=darkblue:b004886f8d][b:b004886f8d]Ou[/b:b004886f8d][/color:b004886f8d] Colocar a Propriedade UpdateMode do DataSetProvider configurado para upWhereAll...
[b:b004886f8d]Eu estou ate o momento pensando que é necessário as duas condiçoes e não uma ou outra...[/b:b004886f8d]
Vinicius2k
17/03/2006
Um exemplo com dados fictícios:
[b:827be97105]Situação 1:[/b:827be97105]
[list:827be97105][*:827be97105]ProviderFlags de todos os TFields = [pfInUpdate, [b:827be97105]pfInWhere[/b:827be97105]]. Ou seja: Todos os campos estarão aptos a estar presentes na cláusula WHERE.
[*:827be97105]UpdateMode do TDataSetProvider = [b:827be97105]upWhereAll[/b:827be97105]. Ou seja: Todos os TFields marcados com pfInWhere estarão na cláusula WHERE.
[*:827be97105]Registro:
ID Nome Cidade UF ------------------------ 1 Vinicius Bicas MG
[*:827be97105]Usuários [b:827be97105]A[/b:827be97105] e [b:827be97105]B[/b:827be97105] trazem o registro para alteração.
[*:827be97105]Usuário [b:827be97105]A[/b:827be97105] altera o nome para [i:827be97105]Marco[/i:827be97105] e aplica os updates.
[*:827be97105]A Midas resolve para a seguinte instrução:
update <TABELA> set NOME = "Marco" where ID = 1 and NOME = "Vinicius" and CIDADE = "Bicas" and UF = "MG"
[*:827be97105]Observe que a instrução resolvida pela Midas levará em conta o valor que o campo NOME possuia no momento que foi trazido para o lado do cliente, no caso, [i:827be97105]Vinicius[/i:827be97105]. Se houver correspondência total do registro na tabela com a cláusula WHERE os updates serão aplicados. No nosso caso, tudo certo. A alteração foi salva.
[*:827be97105]Usuário [b:827be97105]B[/b:827be97105] altera o NOME para [i:827be97105]Martins[/i:827be97105] e aplica os updates.
[*:827be97105]A Midas resolve para a seguinte instrução:
update <TABELA> set NOME = "Martins" where ID = 1 and NOME = "Vinicius" and CIDADE = "Bicas" and UF = "MG"
[*:827be97105]Os updates não serão aplicados pois não haverá correspodência total no WHERE. O valor atual presente no campo NOME deste registro é [i:827be97105]Marco[/i:827be97105], fruto da alteração realizada pelo usuário [b:827be97105]A[/b:827be97105], porém no momento que ele foi trazido pelo usuário [b:827be97105]B[/b:827be97105] o valor do campo NOME [b:827be97105]era[/b:827be97105] [i:827be97105]Vinicius[/i:827be97105].
Qualquer alteração no registro pelo usuário [b:827be97105]A[/b:827be97105] iria impedir que o os updates do usuário B fossem aplicados porque todos os valores são considerados no WHERE. Neste a caso a alteração feita pelo usuário [b:827be97105]A[/b:827be97105] prevalece.[/list:u:827be97105]
[b:827be97105]Situação 2:[/b:827be97105]
[list:827be97105][*:827be97105]ProviderFlags do TField [b:827be97105]ID[/b:827be97105] = [pfInWhere, pfInKey]. Ou seja: O campo ID estará apto a estar presente na cláusula WHERE e está marcado como sendo CHAVE.
[*:827be97105]Opcionalmente[color=red:827be97105][b:827be97105]*[/b:827be97105][/color:827be97105], ProviderFlags dos demais TFields = [pfInUpdate]. Ou seja: Nenhum dos campos restantes estarão aptos a estar presentes na cláusula WHERE.
[*:827be97105]UpdateMode do TDataSetProvider = [b:827be97105]upWhereKeyOnly[/b:827be97105]. Ou seja: Apenas os TFields marcados com pfInWhere e pfInKey estarão na cláusula Where. No nosso caso, apenas [b:827be97105]ID[/b:827be97105].
[color=red:827be97105][b:827be97105]*[/b:827be97105][/color:827be97105] Devido ao UpdateMode do TDataSetProvider, não faz diferença os TFields restantes estarem ou não aptos a estar presentes no WHERE, já que upWhereKeyOnly estabeleceu que apenas os marcados com pfInKey estariam presentes no WHERE.
[*:827be97105]Registro:
ID Nome Cidade UF ------------------------ 1 Vinicius Bicas MG
[*:827be97105]Usuários [b:827be97105]A[/b:827be97105] e [b:827be97105]B[/b:827be97105] trazem o registro para alteração.
[*:827be97105]Usuário [b:827be97105]A[/b:827be97105] altera o nome para [i:827be97105]Marco[/i:827be97105] e aplica os updates.
[*:827be97105]A Midas resolve para a seguinte instrução:
update <TABELA> set NOME = "Marco" where ID = 1
[*:827be97105]Apenas a ID é considerada no WHERE e, no nosso caso, os updates são aplicados com sucesso. A alteração foi salva.
[*:827be97105]Usuário [b:827be97105]B[/b:827be97105] altera o campo NOME para [i:827be97105]Martins[/i:827be97105] e aplica os updates.
[*:827be97105]A Midas resolve para a seguinte instrução:
update <TABELA> set NOME = "Martins" where ID = 1
[*:827be97105]Os updates serão aplicados pois, mesmo que o usuário [b:827be97105]A[/b:827be97105] já tenha alterado o campo NOME para [i:827be97105]Marco[/i:827be97105], apenas o valor de [b:827be97105]ID[/b:827be97105] é analisado e este não foi alterado. Neste caso, a alteração do usuário [b:827be97105]B[/b:827be97105], sobrepõe a do usuário [b:827be97105]A[/b:827be97105].[/list:u:827be97105]
[quote:827be97105=´Marco Salles´]...seria necessário ou Alterar os Flags ou colocar a propriedade UpdateMode do DataSetProvider configurado para upWhereAll...
Eu estou ate o momento pensando que é necessário as duas condiçoes e não uma ou outra...[/quote:827be97105]
Seu pensamento está correto. O resultado efetivo é fruto da combinação das opções. Espero que isto tenha ficado claro no exemplo acima.
Porém, na [b:827be97105]Situação 1[/b:827be97105], de que resolveria o UpdateMode = upWhereAll, se apenas o TField [b:827be97105]ID[/b:827be97105] estivesse marcado para pfInWhere?
E na [b:827be97105]Situação 2[/b:827be97105], de que resolveria o UpdateMode = upWhereKeyOnly se nenhum TField estivesse marcado para pfInKey?
Em ambas as situações, de que adiantaria os usuários A e B alterarem o valor do TField NOME se ele não estivesse marcado para pfInUpdate?
Você poderá observar que ´n´ resultados podem ser obtidos com combinações diferentes destas opções e por este motivo eu sugiro que você as estude com cuidado.
Bem vindo à Midas! :)
Marco Salles
17/03/2006
Mas o que me estranha é o Lado do usuário.... Ele não sabe nada de como está configurado o Banco... O que ele quer é apensa duas coisas
a) Gravar suas alteraçoões
b)Ou Alguma mensagem informando a ele que essas alteraçoes não foram salvas por algum motivo
No Caso que voce especificou na[b:9937e8f939] situação 1 [/b:9937e8f939], O usuário que alterar por ultimo não vai conseguir efetivar esta Alteração ... Fiz uns testes é o método AplyUpdates não gera nenhuma exceção . [b:9937e8f939]Quer Dizer , o cara acha que foi Mas não foi.. ,[/b:9937e8f939]
Mas eu acho que algo deve ser informado para ele...
Pergunto :?: :?: :?: :?:
[b:9937e8f939] É gerada alguma exceção nessa situação onde não há correpondencia total do registro na tabela ?????[/b:9937e8f939]
Vinicius2k
17/03/2006
A não aplicação de updates do TClientDataSet nunca gera uma exceção. Quaisquer erros devem ser capturados e tratados no evento OnReconcileError.
O método ApplyUpdates retorna ainda o número de erros ocorridos na operação, então um teste simples pode ajudar na decisão de exibir algo ao usuário, cancelar uma transação, etc:
if ClientDataSet1.ApplyUpdates(0) > 0 then ShowMessage(´Houve erro.´);
Adriano Santos
17/03/2006
Mas o que me estranha é o Lado do usuário.... Ele não sabe nada de como está configurado o Banco... O que ele quer é apensa duas coisas
a) Gravar suas alteraçoões
b)Ou Alguma mensagem informando a ele que essas alteraçoes não foram salvas por algum motivo
No Caso que voce especificou na[b:584a94970d] situação 1 [/b:584a94970d], O usuário que alterar por ultimo não vai conseguir efetivar esta Alteração ... Fiz uns testes é o método AplyUpdates não gera nenhuma exceção . [b:584a94970d]Quer Dizer , o cara acha que foi Mas não foi.. ,[/b:584a94970d]
Mas eu acho que algo deve ser informado para ele...
Pergunto :?: :?: :?: :?:
[b:584a94970d] É gerada alguma exceção nessa situação onde não há correpondencia total do registro na tabela ?????[/b:584a94970d][/quote:584a94970d]
Galera, tô observamdo este tópico e vi outros do [b:584a94970d]Vinicius2K[/b:584a94970d] sobre a MIDAS e confesso que tô virando fã do Vinicius, rsrs...
Cara tenho conversado com uma galera aqui do trampo e decidimos migrar nosso sistema, por isso estou vendo tudo sobre DBX nos tópicos do fórum e este é bem importante pra mim. Minha dúvida neste ponto é a mesma do Marco, como fazemos? O usuário não quer saber se configuramos o providerflags de um jeito ou de outro, quer é que seja alterado e ponto final. Pensei no seguinte [b:584a94970d]Vinicius[/b:584a94970d], é possível bloquear o registro para alteração, ou deixar read only, mandar mensagem para o usuário ou coisa assim???
Vlw
Martins
17/03/2006
if ClientDataSet1.ApplyUpdates(0) > 0 then ShowMessage(´Houve erro.´);
[b:94bb7d66e3]Vinicius[/b:94bb7d66e3] não imagina o quanto a sua participação neste tópico está sendo importante para nós, está nos ajudando a entender melhor a tecnologia [b:94bb7d66e3]MIDAS[/b:94bb7d66e3] e nos levando a questionamentos para encontrarmos uma maneira de desenvolver um sistema consistente.
Parecido com o q faziamos com o velho Clipper? Tornar um registro exclusivo para fazer alterações e depois liberá-lo?
[b:94bb7d66e3]Vinicius[/b:94bb7d66e3], seria válido usar [b:94bb7d66e3]Reconcile Error Dialog[/b:94bb7d66e3] :?:
Valew!!
Adriano Santos
17/03/2006
Pode crer, mas acho que o Viniciu2K sabe sim o quanto nos ajuda...rsrs... :D
Bem, não programei em clipper, mas imagino isso mesmo. Algo como deixar o registro somente leitura, pois o usuário pode querer visualizar o registro sem alterá-lo. Ou simplesmente não abrir o registro de jeito nenhum, embora ache esta abordagem errada.
[b:73c1250abf]Reconcile Error Dialog[/b:73c1250abf] ??? Nem sabia que tinha isso. Como funciona [b:73c1250abf]Martins[/b:73c1250abf]
Vinicius2k
17/03/2006
Sobre bloquear o registro (bloqueio pessimista), sim é possível. Porém, este bloqueio deve ser feito pelo servidor (na instrução SELECT) e cada SGBD tem um forma particular de efetuar este bloqueio. Por exemplo, IB/FB:
select ID, NOME from <TABELA> where ID = 1 with lock
O registro com ID = 1 ficará trancado até que a transação que o trouxe para o lado do cliente seja finalizada.
Nestes casos, você precisa envolver o SELECT e o posterior UPDATE dentro da mesma transação. O que não é bom pois você terá uma transação muito longa (terá de aguardar o usuário terminar as alterações).
No ponto de vista ´ideal´, esquecendo como *eu* faço, o diálogo de reconciliação, normalmente, é a forma mais correta de tratar estes problemas.
Particularmente, não utilizo diálogo de reconciliação. Sempre que ocorre algum problema, volto a transação, exibo a mensagem ao usuário e tomo a ação [b:d0b6b243db]raCancel[/b:d0b6b243db] no OnReconcileError.
Deve-se apenas observar que a mensagem ao usuário deve ser esclarecedora do problema ocorrido. Analisando o texto de [b:d0b6b243db]E.Message[/b:d0b6b243db] você, quase sempre, consegue determinar o que ocorreu, como uma violação de PK ou de integridade referencial, por exemplo.
O ´problema´, neste caso, é comigo: eu não deixo usuário tomar decisão. Se houver algum problema, eu mesmo cuido para que a consistência dos dados seja mantida e exibo uma mensagem esclarecedora.
Martins
17/03/2006
Pode crer, mas acho que o Viniciu2K sabe sim o quanto nos ajuda...rsrs... :D
[/quote:76741b86da]
:D tb acredito nisso, ele está sendo muito parceiro.
[quote:76741b86da=´Adriano Santos´]
Bem, não programei em clipper, mas imagino isso mesmo. Algo como deixar o registro somente leitura, pois o usuário pode querer visualizar o registro sem alterá-lo. Ou simplesmente não abrir o registro de jeito nenhum, embora ache esta abordagem errada.
[/quote:76741b86da]
No Clipper faziamos isso, usuários [b:76741b86da]A[/b:76741b86da] e [b:76741b86da]B[/b:76741b86da], quando o usuário [b:76741b86da]A[/b:76741b86da] acessava um registro para editá-lo, bloqueavamos o registro (tornando ele exclusivo para aquele usuário) e se o usuário [b:76741b86da]B[/b:76741b86da] tentasse fazer alguma operação no registro, informavamos q aquele registro estava sendo usado por outro usuário, e tão logo o registro fosse liberado o outro usuário poderia utilizá-lo.
[quote:76741b86da=´Adriano Santos´]
[b:76741b86da]Vinicius[/b:76741b86da], seria válido usar [b:76741b86da]Reconcile Error Dialog[/b:76741b86da] :?:
[/quote:76741b86da]
[b:76741b86da]Reconcile Error Dialog[/b:76741b86da] ??? Nem sabia que tinha isso. Como funciona [b:76741b86da]Martins[/b:76741b86da]
É um Dialog, ele é exibido para mostrar as exceptions ao usuário e espera uma ação.
procedure TdmMain.cdsCustomerReconcileError(DataSet: TCustomClientDataSet; E: EReconcileError; UpdateKind: TUpdateKind; var Action: TReconcileAction); begin Action := HandleReconcileError(DataSet, UpdateKind, E); end;
Só não sei se é válido para erros como ´Key violation.´
[b:76741b86da]Vinicius[/b:76741b86da] vc poderia dar mais uma colher aqui :?:
valew!!!!
Adriano Santos
17/03/2006
Eu não estou acompanhando o tópico com um programa de exemplo, apenas lendo, mas vou fazer um exemplo completo pra testar tudo que foi instalado, assim eu participo mais ´ativamente´ do tópico. Legal explanarmos tudo isso.
Adriano Santos
17/03/2006
Opa, instalado não [b:0e36905fba]falado[/b:0e36905fba], tô doidão. :D
Marco Salles
17/03/2006
sobre o problema que de nenhumam mensangem do delphi aparentemente ter sido enviada , no caso da [b:f98410d0a4]situação 1)[/b:f98410d0a4] Tenho algumas coisas a falar
[color=darkblue:f98410d0a4]Lembramos que estamos supondo um Erro , isto é o cara acha que foi mas não foi[/color:f98410d0a4]
1) Dissem por ai que a instrução ApplyUpdates[b:f98410d0a4][color=darkblue:f98410d0a4](0)[/color:f98410d0a4][/b:f98410d0a4] ao ser colocado o zero , não permite erros ... então se algo saiu errado , um erro deveria ser gerado :?: :?: :?: :?:
2)O Martins sugeriu uma caixa de mensagens
3)O Vinicius acresecentou:
Beleza... So que ai vem o [b:f98410d0a4][color=darkblue:f98410d0a4]´X´ [/color:f98410d0a4][/b:f98410d0a4]da questão:
[b:f98410d0a4]Deve-se apenas observar que a mensagem ao usuário deve ser esclarecedora do problema ocorrido. [/b:f98410d0a4]
Deixa eu ver se entendi:::
Para enviar uma mensagem esclaredora voce analisa o contexto a e.message , tipo numa instrução case ou if... E dependendo desse texto , voce exibe a mensagem..[b:f98410d0a4] Nesse caso que estamos tratando seria engraçado , mas a mensagem deve ser assim [/b:f98410d0a4]:
[b:f98410d0a4]Olha cara , voce tem que atualizar e so depois fazer estas alteraçoes , mas tem que ser rápido , porque se alguem da Rede fizer primeiro que voce , voce vai receber esta mensagem denovo.... Xiiiiiiiiiiiiiiiiiiiiiiiii[/b:f98410d0a4]
:?: :?: :?: :?: :?: :?: :?:
É Claro que isto é uma brincadeira , mas a mensagem não deve fugir muito desse contexto...Nesse esquema o cara dependendo dos acessos fica sempre amarrado , numa situação pessimista do caso.. [b:f98410d0a4]Ou eu to errado [/b:f98410d0a4]:?: :?: :?:
[color=darkblue:f98410d0a4]Importantissimo :[/color:f98410d0a4]
[b:f98410d0a4]Outro ponto chave vinicius , para a perfeita compreessão do tópico , suponha que
Usuário A , faça alçteraçoes nun registro X
Usuário B : Faz alteraçoes em Tres Registro : X , Y , Z
o usuário A Efetiva Primeiro , o Usuário B efetiva depois
Com estmos vendo as alteraçoes dos Registros X , do usuário B , não sera efetivada... Mas e as outras alteraçoes(Y , Z) Serão efetivadas independemente , ou serão abortadas por uma instrução de ação raCancel no OnReconcileError. ???????[/b:f98410d0a4]
Adriano Santos
17/03/2006
1) Dissem por ai que a instrução ApplyUpdates[b:19957788f1][color=darkblue:19957788f1](0)[/color:19957788f1][/b:19957788f1] ao ser colocado o zero , não permite erros ... então se algo saiu errado , um erro deveria ser gerado :?: :?: :?: :?:
[/quote:19957788f1]
Marco, me permita fazer uma correção aqui. Pelo que vi nos tópicos que acompanhei do Vinicius e outros colegas também não é que o ApplyUpdates(0) não permite erros, na verdade o ApplyUpdates de modo geral nunca gera exceção, mas se colocarmos ApplyUpdates(-1) ele nunca retornará o erro ocorrido, diferente de ApplyUpdates(0).
[quote:19957788f1=´Marco Salles´]
[b:19957788f1]Olha cara , voce tem que atualizar e so depois fazer estas alteraçoes , mas tem que ser rápido , porque se alguem da Rede fizer primeiro que voce , voce vai receber esta mensagem denovo.... Xiiiiiiiiiiiiiiiiiiiiiiiii[/b:19957788f1]
[/quote:19957788f1]
Boa resposta ao usuário :D
[quote:19957788f1=´Marco Salles´]
:?: :?: :?: :?: :?: :?: :?:
É Claro que isto é uma brincadeira , mas a mensagem não deve fugir muito desse contexto...Nesse esquema o cara dependendo dos acessos fica sempre amarrado , numa situação pessimista do caso.. [b:19957788f1]Ou eu to errado [/b:19957788f1]:?: :?: :?:
[/quote:19957788f1]
Pode crer, aki o usuário vai ficar ´p´ da vida pq não consegue fazer nada com o registro, estamos certos?
[quote:19957788f1=´Marco Salles´]
[color=darkblue:19957788f1]Importantissimo :[/color:19957788f1]
[b:19957788f1]Outro ponto chave vinicius , para a perfeita compreessão do tópico , suponha que
Usuário A , faça alçteraçoes nun registro X
Usuário B : Faz alteraçoes em Tres Registro : X , Y , Z
o usuário A Efetiva Primeiro , o Usuário B efetiva depois
Com estmos vendo as alteraçoes dos Registros X , do usuário B , não sera efetivada... Mas e as outras alteraçoes(Y , Z) Serão efetivadas independemente , ou serão abortadas por uma instrução de ação raCancel no OnReconcileError. ???????[/b:19957788f1]
[/quote:19957788f1]
Essa também quero saber, mas imagino que o controle é individual, ou seja, os registros Y e Z devem ser confirmados normalmente pela MIDAS.
Vinicius2k
17/03/2006
[b:77a504b397]ApplyUpdates(0)[/b:77a504b397]: não podem haver erros. Qualquer erro que ocorra, a aplicação das atualizações será paralizada.
[b:77a504b397]ApplyUpdates(´n´)[/b:77a504b397]: podem haver ´n´ erros. Quando o número de erros atingir ´n´, a aplicação das atualizações será paralizada.
[b:77a504b397]ApplyUpdates(-1)[/b:77a504b397]: não importa quantos erros existirem, o que puder ser aplicado será.
Nas duas situações aonde é possível que a atualização seja paralizada, o evento OnReconcileError é disparado e você, ou o usuário, tem opção de tomar alguma providência.
Vocês devem compreender que, apesar da prática mais comum ser aplicar os updates imediatamente após os post do registro local, isto não é obrigatório. Você pode inserir 1000 registros e só então enviá-los para o banco de dados, por exemplo. Isto só é muito arriscado no dia-a-dia pois pode haver perda de trabalho, mas é o mais indicado em casos como importação de arquivos texto, por exemplo.
Sobre a dúvida em relação aos registros X, Y, Z cada registro é tratado de forma independente pela Midas. A ação [b:77a504b397]raCancel[/b:77a504b397] aborta apenas o registro que ocasionou o erro de reconciliação, para abortar todo o update você teria que invocar o método CancelUpdates do TClientDataSet.
Só uma opinião particular: [b:77a504b397]vocês estão maximizando o problema de alteração de um mesmo registro por diversos usuários [/b:77a504b397]. Esta situação não deve ser frequente e se for o erro está partindo do projeto.
Na ´vida real´ se seu projeto exige que diversos usuários fiquem alterando os registros uns dos outros o dia todo, pense bem pois foi você que errou no seu projeto.
*EU* só uso bloqueio pessimista quando estritamente necessário e não permito que o usuário tome nenhuma decisão. Se ocorrer algum erro, desfaço tudo e ele que faça novamente. Juntando todos os meus clientes, devo ter cerca de 1200 usuários e cada cliente com uma média de 25 usuários. *EU* não tenho problemas com isso.
Martins
17/03/2006
Marco, me permita fazer uma correção aqui. Pelo que vi nos tópicos que acompanhei do Vinicius e outros colegas também não é que o ApplyUpdates(0) não permite erros, na verdade o ApplyUpdates de modo geral nunca gera exceção, mas se colocarmos ApplyUpdates(-1) ele nunca retornará o erro ocorrido, diferente de ApplyUpdates(0).
[/quote:be671a7309]
Isso mesmo
[quote:be671a7309=´Marco Salles´]
[b:be671a7309]Olha cara , voce tem que atualizar e so depois fazer estas alteraçoes , mas tem que ser rápido , porque se alguem da Rede fizer primeiro que voce , voce vai receber esta mensagem denovo.... Xiiiiiiiiiiiiiiiiiiiiiiiii[/b:be671a7309]
[/quote:be671a7309]
Gostei dessa!!! :D
[quote:be671a7309=´Marco Salles´]
[color=darkblue:be671a7309]Importantissimo :[/color:be671a7309]
[b:be671a7309]Outro ponto chave vinicius , para a perfeita compreessão do tópico , suponha que
Usuário A , faça alçteraçoes nun registro X
Usuário B : Faz alteraçoes em Tres Registro : X , Y , Z
o usuário A Efetiva Primeiro , o Usuário B efetiva depois
Com estmos vendo as alteraçoes dos Registros X , do usuário B , não sera efetivada... Mas e as outras alteraçoes(Y , Z) Serão efetivadas independemente , ou serão abortadas por uma instrução de ação raCancel no OnReconcileError. ???????[/b:be671a7309]
[/quote:be671a7309]
Também penso o mesmo q o Adriano, acho q Y eZ seriam confirmadas.
Marco Salles
17/03/2006
[b:df1754af97]Testes simples que eu fiz isto não se confirmou esta hipotese...[/b:df1754af97]
Fiz o seguinte , rodei o projeto e foi na pasta do aplicativo e rodeu o executável
No Registro x Acessei pelo projeto e pelo executável da pasta acessei o mesmo registro x e mais dois outros registros ...Salvei primeiro pelo projeto e depois pelo executável... Gerou o erro como deveria , porem , fechei o projeto e abri de novo e os dois registros Y E Z permaneceram inalterados , contrariando o que supostamente voce acreditaram...
Martins
17/03/2006
[b:0316e6b70f]Testes simples que eu fiz isto não se confirmou esta hipotese...[/b:0316e6b70f]
Fiz o seguinte , rodei o projeto e foi na pasta do aplicativo e rodeu o executável
No Registro x Acessei pelo projeto e pelo executável da pasta acessei o mesmo registro x e mais dois outros registros ...Salvei primeiro pelo projeto e depois pelo executável... Gerou o erro como deveria , porem , fechei o projeto e abri de novo e os dois registros Y E Z permaneceram inalterados , contrariando o que supostamente voce acreditaram...[/quote:0316e6b70f]
:shock:
Foi mesmo [b:0316e6b70f]Marco[/b:0316e6b70f] :?: Poxa pensei o contrário :cry: , mas tudo bem, pelo menos já temos a informação de como se comprta, excelente.
Bem vamos ver agora o q vem para aprendermos mais e mais.
Marco Salles
17/03/2006
[b:71d62d8f78]Não foi so seu nã .. Foi meu tambémm[/b:71d62d8f78]
Veja o comando de Applypdates que estamos dando
ApplyUpdates(0): não podem haver erros. Qualquer erro que ocorra, a aplicação das atualizações será paralizada.
Pois é ... Sera paralizada.... Para que isto não ocorra , no nosso caso do X,Y,Z temos que dar um comando assim
ApplyUpdates(1):.. Com isso mesmo que se tenha um erro , que no caso foi o acesso pelo usuário B ao Regiistro X , os registros Y e Z seráo atualizados...Isto não é fantático.. Pode testar que dá
Eu testei com dois erros , tres erros alterando o ApplyUpdates e foi D+
Agora , acho que isto tb esta definido , volto a um outro assunto
Mas olha so uma situação , onde estas ediçoes podem ocorrer e não sei a priori como evitar
Suponha que se tenha um empresa onde cada vendedor faça uma venda
Pode existir uma situação onde em uma determinada venda dois ou mais vendedores , farão a venda de um Mesmo produto... O aplicativo deve então , editar a quantidade do Produto numa Tabela de Estoque desse produto... Isto não geraria um ERRO ::::: Ou eu to enganado ????
ApplyUpdates(´n´): podem haver ´n´ erros. Quando o número de erros atingir ´n´, a aplicação das atualizações será paralizada.
ApplyUpdates(-1): não importa quantos erros existirem, o que puder ser aplicado
Adriano Santos
17/03/2006
Suponha que se tenha um empresa onde cada vendedor faça uma venda
Pode existir uma situação onde em uma determinada venda dois ou mais vendedores , farão a venda de um Mesmo produto... O aplicativo deve então , editar a quantidade do Produto numa Tabela de Estoque desse produto... Isto não geraria um ERRO ::::: Ou eu to enganado ????
[/quote:41b3df11d8]
É verdade, um dos vendedores vai vender mas a quantidade deste produto no estoque vai ficar errada. Neste caso não seria interessante fazer o controle de transação no momento da gravação do registro? ´Bloqueando´ o registro por alguns segundos até que o usuário A ´libere´ o registro?
Martins
17/03/2006
Suponha que se tenha um empresa onde cada vendedor faça uma venda
Pode existir uma situação onde em uma determinada venda dois ou mais vendedores , farão a venda de um Mesmo produto... O aplicativo deve então , editar a quantidade do Produto numa Tabela de Estoque desse produto... Isto não geraria um ERRO ::::: Ou eu to enganado ????
[/quote:2c8f5321d0]
Não sei, sinceramente não sei responder essa, mas acho q depende muito de como foram projetadas as regras de negócio, de como são feitas as transações, coisas assim.
Vinicius2k
17/03/2006
A situação proposta em relação ao estoque de um produto é o mais clássico erro sobre como manipular os dados em ambiente multi-usuário.
No caso, o que vocês imaginam? Abrir a tabela de estoque do produto, localizar o produto vendido, alterar sua quantidade e aplicar os updates? Se for isso, com certeza vocês terão problemas e como eu disse antes, o erro partiu do projeto. Isto não deve ser feito.
Esta é a forma correta... Sempre referenciar a quantidade atual e aplicar adição ou subtração da quantidade de entrada ou saida, logicamente utilizando uma instrução SQL:
update ESTOQUEPRODUTO set QTDE = QTDE - :qtdevenda where IDPRODUTO = :idproduto
Executando esta instrução eu garanto que nunca nenhum de vocês terá problemas com quantidade errada em estoque causada por erros da aplicação.
Se você preferir, pode inserir esta instrução em uma Trigger ´After Insert´ da tabela de itens da venda. Mesma segurança, com mais agilidade.
Lembrem-se que as transações entram em ´filas´ para serem executadas no servidor, então, mesmo que 10 usuários salvem sua venda no mesmo milésimo de segundo, a cada execução a quantidade será atualizada baseando-se na quantidade existente, ou seja: 99,99¬ de chances não haver erro (0,01¬ de chances reservado para ´coisas da informática´). Cada SGBD gerencia esta ´fila´ de forma diferente, mas nunca um mesmo registro é editado ao mesmo tempo por mais de uma transação.
E... Transações. Sempre envolvam operações dependentes dentro da mesma transação. Face a qualquer erro volte toda a transação e exiga o re-trabalho do usuário.
Isto garante que o que você fez (o projeto) sempre funcionará e estará íntegro, independente do aqueles ´bichinhos´ chamados usuários tentarem fazer. :)
Sugestão de leitura:
http://www.comunidade-firebird.org/cflp/downloads/CFLP_O034.PDF
http://www.comunidade-firebird.org/cflp/downloads/CFLP_T032.PDF
Martins
17/03/2006
Valew [b:5c53ec90d1]Vinicius[/b:5c53ec90d1], agora captei vossa mensagem inestimado guru, hehehe :)
Perfeitamente compreendido, e obrigado por ter indicado material sobre o assunto das transações.
valew :wink:
Marco Salles
17/03/2006
Valeu vinicius... Acho que agora fui
Adriano Santos
17/03/2006
update ESTOQUEPRODUTO set QTDE = QTDE - :qtdevenda where IDPRODUTO = :idproduto
Posso sugerir um exemplo prático e detalhado Vinicius pra ver se entendi totalmente?
Tabelas do sistema:
Itens de Pedido e Estoque
O usuário inicia o processo de venda. Inclui os produtos A, B e C na tabela Itens de Pedido. No momento em que o pedido é confirmado, ou seja, gravado por completo faço um update na tabela (update sugerido acima) usando o código do produto (presente em Itens de Pedido) como ID certo?? Algo como:
procedure TfPrincipal.Button4Click(Sender: TObject); var Transacao : TTransactionDesc; begin try Transacao.TransactionID := 1; Transacao.IsolationLevel := xilREADCOMMITTED; cdsItensPedido.First; while not cdsItensPedido.EOF do begin //update da sqlQueryAtualiza //UPDATE ESTOQUE SET QTDE = :QTDE WHERE ID_PRODUTO = :ID_PRODUTO_VENDIDO; with sqlQueryAtualiza do begin ParamByName(´ID_PRODUTO_VENDIDO´).AsFloat := cdsItensPedido.FieldByName(´CODIGO´).AsFloat; ParamByName(´QTDE´).AsFloat := cdsItensPedido.FieldByName(´CODIGO´).AsFloat; sqlConnection.StartTransaction(Transacao); ExecSql; sqlConnection.Commit(Transacao); end; cdsItensPedido.Next; end; except on E:EDataBaseError do begin ShowMessage(´Erro na atualização do estoque:´ + #1313 + E.Message); sqlConnection.RollBack(Transacao); end; end; end;
Aqui estou controlando a transação sozinho, certo????
Vinicius2k
17/03/2006
[list:7e08ebba7b][*:7e08ebba7b]Abra e digite o pedido em [b:7e08ebba7b]cdsPedido[/b:7e08ebba7b].
[*:7e08ebba7b]Digite os ítens deste pedido em [b:7e08ebba7b]cdsPedidoItem[/b:7e08ebba7b].
[*:7e08ebba7b]Abra uma transação.
[*:7e08ebba7b]Salve o pedido (AppyUpdates do [b:7e08ebba7b]cdsPedido[/b:7e08ebba7b]) obtendo a ID do pedido.
[*:7e08ebba7b]Utilize a ID obtida e percorra o [b:7e08ebba7b]cdsPedidoItem[/b:7e08ebba7b] de editando os ítens preechendo o campo destinado a receber a ID do Pedido.
[*:7e08ebba7b]Salve os ítens (ApplyUpdates do [b:7e08ebba7b]cdsPedidoItem[/b:7e08ebba7b]).
[*:7e08ebba7b]Crie uma query com a instrução:
update PRODUTOESTOQUE set QTDE = QTDE - :qtdevenda where IDPRODUTO = :idproduto
Atenção para a instrução! A que você propõe não leva em conta a quantidade existente...
[*:7e08ebba7b]Percorra o [b:7e08ebba7b]cdsPedidoItem[/b:7e08ebba7b] passando os parametros [b:7e08ebba7b]qtdevenda[/b:7e08ebba7b] e [b:7e08ebba7b]idproduto[/b:7e08ebba7b] para a query, e executando-a a cada passagem de parametros.
[*:7e08ebba7b]Tudo certo? Feche a transação com commit.
[*:7e08ebba7b]Algum erro? Volte toda a transação desde a aplicação dos updates do [b:7e08ebba7b]cdsPedido[/b:7e08ebba7b].[/list:u:7e08ebba7b]
Um exemplo de código, usando dbExpress, seria este:
... var Transacao : TTransactionDesc; begin Transacao.TransactionID := 1; Transacao.IsolationLevel := xilREADCOMMITTED; try SeuSQLConnection.StartTransaction(Transacao); if cdsPedido.ApplyUpdates(0) > 0 then begin cdsPedido.Refresh; with cdsPedidoItem do begin First; while not EOF do begin Edit; FieldByName(´IDPedido´).AsInteger := cdsPedido.FieldByName(´IDPedido´).AsInteger; Post; Next; end; end; if cdsPedidoItem.AppyUpdates(0) > 0 then begin with cdsPedidoItem do begin First; try while not EOF do begin SuaQuery.ParamByName(´idproduto´).AsInteger := FieldByName(´IDProduto´).AsInteger; SuaQuery.ParamByName(´qtdevenda´).AsInteger := FieldByName(´QtdeVenda´).AsInteger; SuaQuery.ExecSQL; Next; end; // Finalmente o Commit se tudo deu certo. SeuSQLConnection.Commit(Transacao); except // Mensagem ao usuário. Erro atualizando estoque. SeuSQLConnection.Rollback(Transacao); end; end; end else begin // Mensagem ao usuário. Erro gravando ítens. SeuSQLConnection.Rollback(Transacao); end; end else begin // Mensagem ao usuário. Erro granvando pedido. SeuSQLConnection.Rollback(Transacao); end; except // Mensagem ao usuário. Erro em algum bloco do código desde a // abertura da transação. SeuSQLConnection.Rollback(Transacao); end; end;
Como eu disse, este exemplo é todo ´no braço´. Se você usar Nested Datasets e/ou utilizar uma Trigger para atualizar o estoque no ´After Insert´ da tabela PedidoItem, o código ficará bem mais ´enxuto´.
PS: não testei o código, então perdõe-me algum erro de sintaxe.
Adriano Santos
17/03/2006
Ufa, acho que tô aprendendo...:D
Acho que sou ignorante, que isso? Nested Datasets ?
Eu tinha entendido o esquema do QTDE = QTDE - :QTDE, mas na hora de montar o exemplo esqueci, porém ficou claro pra mim.
Com trigger sei fazer, embora eu seja contra o uso de programação no banco de dados quando se desenvolve um projeto pensando em multi-banco, mas esta é uma outra questão que podíamos discutir mais tarde.
vlw
Vinicius2k
17/03/2006
Resumindo: Mestre/Detalhe ao ´jeito Midas de ser´.
Veja, discussão recente:
http://forum.clubedelphi.net/viewtopic.php?t=75051
Marco Salles
17/03/2006
Vamos supor que se insere dados atraves de um dbgridPedidos e também o DbGridItemProdutos
1)Antes de gerar o Pedido Incio a [b]transação[/b] 2)Para cada que se for inserido na tabela Produtos , na hora do post executo update ESTOQUEPRODUTO set QTDE = QTDE - :qtdevenda where IDPRODUTO = :idproduto 3)apos finalizar dou um ApplyUpdates e por fim finalizo a transação
:?: :?: :?: :?: :?:
Bem , eu sei que pode ter algum erro grave nestar estrutura , mas eu não vejo o porque de ficar nessa estrutura de loop ???? eu não estou conseguindo entender , ja que applyupdates grava tanto os dados da tabela mestre quanto os dados da tabela detalhe e a correção na tabela Estoque pode ser feito dando um post na tabela ItemPedidos :?: :?:
Descupe mas não to captando.... :cry: :cry: :cry:
Adriano Santos
17/03/2006
Vamos supor que se insere dados atraves de um dbgridPedidos e também o DbGridItemProdutos
1)Antes de gerar o Pedido Incio a [b]transação[/b] 2)Para cada que se for inserido na tabela Produtos , na hora do post executo update ESTOQUEPRODUTO set QTDE = QTDE - :qtdevenda where IDPRODUTO = :idproduto 3)apos finalizar dou um ApplyUpdates e por fim finalizo a transação
:?: :?: :?: :?: :?:
Bem , eu sei que pode ter algum erro grave nestar estrutura , mas eu não vejo o porque de ficar nessa estrutura de loop ???? eu não estou conseguindo entender , ja que applyupdates grava tanto os dados da tabela mestre quanto os dados da tabela detalhe e a correção na tabela Estoque pode ser feito dando um post na tabela ItemPedidos :?: :?:
Descupe mas não to captando.... :cry: :cry: :cry:[/quote:49546d0fef]
E ai Marco belê??
Mano imagina a seguinte situação:
[quote:49546d0fef=´Itens de Pedido´]
Codigo| Descrição | Qtde Comprada
001 | Lápis | 10
002 | Caneta | 1
003 | Borracha | 7
[/quote:49546d0fef]
[quote:49546d0fef=´Produtos do Estoque´]
Codigo | Descrição | Qtde Estocada
001 | Lápis | 20
002 | Caneta | 2
003 | Borracha | 10
[/quote:49546d0fef]
A estrutura proposta para fazer a atualização no estoque proposta pelo Vinicius seria:
[quote:49546d0fef=´Estrutura do update´]
UPDATE
ESTOQUE
SET
QTDE = QTDE - :QTDE_VENDIDA
WHERE
CODIGO = :COD_PROD_VENDA
[/quote:49546d0fef]
Se a gente traduzir isso ficaria
[quote:49546d0fef=´Estrutura do update´]
UPDATE
ESTOQUE
SET
QTDE = QTDE - 10
WHERE
CODIGO = 001
[/quote:49546d0fef]
Ou seja, eu teria que fazer um update para cada produto a ser atualizado no estoque, pois na mesma instrução SQL eu não conseguirir atualizar o estoque dos três produtos de uma só vez. Então a gente faz um LOOP na tabela Itens de Pedido e a cada passagem mudamos os parâmetros e executamos a query.
Estava pensando como você, usando POST, mas isso podem ocorrer erros, pois os registros 001, 002 e 003 estão carregados no cliente/estação dependendo de um POST para serem atualizados. Ora, partindo da explicação do Vinicius temos o seguinte caso:
O usuário A faz a venda dos produtos 001, 002 e 003, nas quantidades 5, 3 e 2 respectivamente. Imagina que pouco antes do post outro usuário faz vendas do mesmo produto em quantidades diferentes e efetua a operação, neste ponto o usuário A faz o post sem levar em conta a quantidade atual. Pimba, erro. O produto 001 tinha 20 no estoque -5 que o usuário B vendeu = 15. Quando o usuário A ´postar´ ele ainda terá o valor 20 do estoque na memória, ou seja, 20 - 10 = 10. Diferente da quantidade que realmente existe no estoque, que deveria ser 5 (10 que o usuário A vendeu e 5 que o usuário B vendeu).
Com o update proposto pelo Vinicius a história é diferente, porque o tempo em que o sistema levará para aplicar as modificações é em milésimos de segundo e você ainda leva em consideração a qtde que está realmente no estoque.
Resumindo: O update vai atualizar a quantidade usando QTDE = QTDE - :VALOR_PASSADO, isso o tempo todo. Você pode ter certeza que nunca ocorrerá problemas de estoque incosistente.
Tô certo Vinícius, nosso grande guru...rsrs :D
Martins
17/03/2006
Imagine o seguinte, sem o modelo proposto pelo [b:63db3ea3b9]Vinicius[/b:63db3ea3b9].
[quote:63db3ea3b9=´Produtos Estoque´]
Codigo | Descrição | Qtde Estocada
001..... | Lápis....... | 20
002..... | Caneta.... | 2
003..... | Borracha. | 10 [/quote:63db3ea3b9]
[quote:63db3ea3b9=´Itens Pedido - Usuário A´]
Codigo| Descrição | Qtde Comprada
001.... | Lápis....... | 10
002.... | Caneta.... | 1
003.... | Borracha. | 7
[/quote:63db3ea3b9]
[quote:63db3ea3b9=´Itens Pedido - Usuário B´]
Codigo| Descrição | Qtde Comprada
001.... | Lápis....... | 15
002.... | Caneta.... | 2
003.... | Borracha. | 4
[/quote:63db3ea3b9]
Imaginemos como o Adriano falou q os usuário A e B estão fazendo as vendas dos mesmo produtos, ainda mais nesse período de reinicio das aulas, sem atualizar constantemente o Estoque para ter sempre o REAL, deixando para atualizar somente após todos os lançamentos com um POST.
Para ambos a quantidade no estoque vai ser a q estava antes, no momento da abertura, só q quem fechar a venda por último vai receber a mensagem de erro, vai deixar seu estoque negativo. Por isso a necessidade de se projetar de forma correta um sistema multi-usuário, pq se fosse em um sistema mono-usuário, não haveria esse problema.
Vamos esperar pelas colocações do [b:63db3ea3b9]Vincius[/b:63db3ea3b9], nosso mentor.
Marco Salles
17/03/2006
Mas me permite tentar entender...
Esta situação de falta de produto é algo questionavel... Tanto é que no artigo sugerido para a leitura , No Estudo Do Caso1 tem um paragrafo interresante:
´[b:d9edd5bf37]Se por exemplo um cliente chega no caixa com um produto na mão , não há o que ser validado em termos de estoque.. A prova da disponibilidade do Produto esta na mãos do Cliente[/b:d9edd5bf37]´... Então esta soma de quantidades não me convence ....
E outra , não adianta nada atualizar , se o usuário não der um refresh , ele não vai ver a quantidade atual... Lembramos que estamos trabalhando em CacheUpdates com o ClinteDataSet
Por outro LADO , li nun tópico do vinicius o seguinte:
http://forum.devmedia.com.br/viewtopic.php?t=49234&highlight=isolationlevel&sid=121432fbde342a60d8095a842ae3f0d4
citação do viniius que me chamou atenção no outro Tópico:
Ai o problema esta na estrutura Do Dbexpress e não na lógica do negócio....
ta meio confuso não ???
Adriano Santos
17/03/2006
Mas me permite tentar entender...
[/quote:94d9886720]
Permito, claro :D.
[quote:94d9886720=´Marco Salles´]
´Se por exemplo um cliente chega no caixa com um produto na mão , não há o que ser validado em termos de estoque.. A prova da disponibilidade do Produto esta na mãos do Cliente...´
[/quote:94d9886720]
Isso se for um PDV, mesmo assim ainda tem uma outra coisa. Imagina que o cliente chegou no caixa com o produto A, mas tem um outro cliente circulando pela loja que pegou o produto A, ou seja, não tem como você saber quanto tem em estoque pois o cara pode ficar horas na loja e com o produto na mão.
Neste caso de PDV, eu concordo contigo, é bem difícil de controlar. Porém se for um sistema onde o cliente pode comprar via telefone ai a coisa muda. Eu trabalhei em um lugar onde funcionava assim o sistema de vendas.
[quote:94d9886720=´Emissão de Pedido de Vendas´]
[list:94d9886720]
[*:94d9886720]Cliente liga para a empresa e diz que quer 3 caixas de parafusos tipo A.
[*:94d9886720]Usuário abre pedido e começa a inserir os itens de pedido.
[*:94d9886720]Usuário finaliza o pedido de vendas, imprime e emite ordem para o estoque.
[*:94d9886720]Estoque recebe ordem e separa o produto para enviar.
[/list:u:94d9886720]
[/quote:94d9886720]
Ou seja, neste caso não há um cliente na boca do caixa com o produto na mão e como disse anteriormente, mesmo que tivesse como vou saber a quatidade em estoque, já que o cliente B pode estar circulando com o produto na mão e de repente não querer mais levá-lo. E pior, deixá-lo em outra parte qualquer do mercado, tipo um carrefour da vida...danou-se tudo. :D
[quote:94d9886720=´Marco Salles´]
Ta meio confuso não ???
[/quote:94d9886720]
Confuso tah, mas acho que estamos perto de chegar a uma conclusão concreta de tudo.
Vinicius2k
17/03/2006
Eu já não estou mais entendendo o que vocês estão discutindo... :shock:
Eu não estou garantindo que o seu estoque estará sempre correto devido a uma pequena proteção ter sido feita na aplicação. Estoque depende de ´n´ outras variantes humanas que não são contempladas num simples enlace de updades e controle transacional.
Eu GARANTO com aquilo que nada passará pela aplicação sem que tenha sido dada sua baixa de estoque. O resto, só Deus sabe :)
Marco, sobre o laço que você diz não ter compreendido, a razão:
Voce grava o pedido, grava seus ítens. [b:565e3be7cc]E a baixa do estoque?[/b:565e3be7cc] Você não vai baixar o estoque só por ter gravado os ítens... não no modelo proposto, aonde existe um campo em alguma tabela responsável por armazenar a quantidade atual.
Faz-se necessário um laço nos ítens baixando em uma outra tabela as quantidades do estoque.
Sobre minha citação em outro tópico, ela sozinha fica um tanto fora de contexto... se você observar, eu estava demonstrando como controlar a transação com dbExpress (sem uso da Midas), executando instruções diretamente. Volto a lembrar: dbExpress é uma coisa, Midas é outra.
Sobre o que eu sei em transações no dbExpress (com e sem Midas), aqui no fórum, leiam:
http://forum.devmedia.com.br/viewtopic.php?t=58547
Marco Salles
17/03/2006
Mas é isto que estou batendo na tecla....É isso que entendi....
Sim , mas apenas o Laço do CdDetlahe ja é o suficiente , e alem disso um Applyupdates No CdMestre ja basta
Vou ler agora...
Adriano Santos
17/03/2006
É exatamente ai que queria chegar Vinicius, era esta a explicação. E a baixa do estoque? Só porque eu inclui um produto da tabela itens de pedido não significa que preciso debitar do estoque, pois o cara pode simplesmente cancelar a operação.
Como você disse, existem ´n´ operações humanas que podem fazer o estoque, assim como outras coisas dentro do sistema, falharem, é absolutamente normal.
Acho que pra mim as dúvidas estão mais que sanadas.
vlw
Marco Salles
17/03/2006
Fiz um exmplo de Mestre Detalhe e coloquei o código
no SqlQuery esta assim :
Update PRODUTO set Saldo=Saldo - :quant Where Cod_Produto=:Codigo
procedure TForm1.Button1Click(Sender: TObject); var TransDesc:TTransactionDesc; begin TransDesc.TransactionID:=1; TransDesc.IsolationLevel:=xilREADCOMMITTED; SQLConnection1.StartTransaction(TransDesc); try clientDataSet1.ApplyUpdates(0); //do mestre with clientDataSet2 do // do detalhe begin First; while not eof do //atualização do estoque begin SQLQuery1.Params[0].asinteger:= clientDataSet2.FieldByName(´Quantidade´).asinteger; SQLQuery1.Params[1].AsInteger:= clientDataSet2.FieldByName(´Cod_Produto´).asinteger; SQLQuery1.ExecSQL; Next; end; end; sqlconnection1.Commit(TransDesc); except sqlConnection1.Rollback(TransDesc); end; end;
O Exemplo que voces pasaram tinham dois Applyupdates e dos laçoes eu fiquei pensando para que tudo aquilo ????
Vinicius2k
17/03/2006
E eu também disse que se fosse usando Nested Datasets e/ou trigger o código ficaria enxuto...
Martins
17/03/2006
Valew
Martins
17/03/2006
Correto, essa afirmação pode ser vista nos seguintes trechos.
Azimute-al
17/03/2006
Obrigado Vinicius!