Newsgroup: it.comp.lang.delphi
Titolo: ADOQuery: problemi con refresh dati. Cache???

Ciao a tutti,
in una applicazione che sto sviluppando ho la necessità di aggiornare
alcune label dopo determinate operazioni; a queste label (caption)
vengono assegnati i valori dei rispettivi Count SQL ma per qualche
strano motivo a volte il Count non corrisponde al numero di record
presenti nel DB (Access). La query è banalissima per cui escludo al
1000% problemi di SQL; può essere relativo a qualche forma di cache
memorizzata nella ADOQuery? Quando capita questo disallineamento,
basta eseguire nuovamente la query e il valore si aggiorna
correttamente ma l'anomalia può portare a diversi problemi che mi
costringono a trovare una soluzione.
Qualcuno ha idee in merito? Avete già riscontrato un problema simile?
Grazie mille.


Giusto per chiarezza:
  ADOQueryAccettazioni.Active := false;
  ADOQueryAccettazioni.SQL.Clear;
  ADOQueryAccettazioni.SQL.Add('SELECT Cod_tracer FROM [t_tracer]
WHERE [Inviato] = false');
  ADOQueryAccettazioni.Open;
  ADOQueryAccettazioni.Active := true;
  if ADOQueryAccettazioni.RecordCount > 0 then
    BottoneInviaAccettazioni.Enabled := true
  else
    BottoneInviaAccettazioni.Enabled := false;
  //
  LabelAccettazioni.Caption :=
IntToStr(ADOQueryAccettazioni.RecordCount);
Questa è l'SQL e come potete vedere è di una semplicità estrema per
cui l'anomalia non dipende sicuramente dalla select.
Grazie ancora.

 cui l'anomalia non dipende sicuramente dalla select.
Incomincia a usare la query giusta:
SELECT COUNT(Cod_tracer) FROM [t_tracer] WHERE [Inviato] = false
e non ti fidare del recordcount, pu� avere effetti collaterali quali un 
fetchall non eseguito da ACCESS (brrrrr............. ho i brividi)
:)
-- 
morde
D5,D6,D7
QT 4.7.0 - MingW- QT Creator 2.1

 IntToStr(ADOQueryAccettazioni.RecordCount);
Se vuoi il numero fai una query con un select count(1) blabla bla....
E usi quella.
Infine, un suggerimento...
Questo codice alla lunga ti porta in confusione:
IF QUALCOSA THEN
   FLAG := TRUE
ELSE
  FLAG := FALSE
Meglio questo;
IF QUALCOSA THEN
begin
   FLAG := TRUE
end
ELSE
begin
  FLAG := FALSE
end;
o questo:
flag := false
IF QUALCOSA THEN
begin
   FLAG := TRUE
end;
A.


Grazie per la segnalazione ma non deriva sicuramente da questo il
problema, ho provato anche usando il COUNT SQL oltre al recordcount
nella forma attuale ma non ho risolto comunque. In ogni caso rimetto
il COUNT SQL segnalato da te così evito ulteriori anomalie. Grazie.

 A.
non è più leggibile ?
flag := Qualcosa;


Concordo sulla questione che Access non sia un DB ma non posso passare
ad altro per esigenze del cliente.
Questo è il codice originale prima delle varie prove fatte per
risolvere l'anomalia:
  ADOQueryAccettazioni.Active := false;
  ADOQueryAccettazioni.SQL.Clear;
  ADOQueryAccettazioni.SQL.Add('SELECT COUNT(ID) AS CountAccettazioni
FROM [t_tracer] WHERE [Inviato] = false');
  ADOQueryAccettazioni.Open;
  ADOQueryAccettazioni.Active := true;
  if ADOQueryAccettazioni.FieldByName('CountAccettazioni').AsInteger >
0 then
    BottoneInviaAccettazioni.Enabled := true
  else
    BottoneInviaAccettazioni.Enabled := false;
  LabelAccettazioni.Caption :=
ADOQueryAccettazioni.FieldByName('CountAccettazioni').AsString;
Dovrebbe essere in linea con le vostre segnalazioni ma il problema
c'era e rimane.
Grazie comunque della segnalazione, info utili a prescindere dal
problema.

 c'era e rimane.
Scusa, ma se fai la query direttamente da access cosa succede?
A.


Succede che funziona.
Anche dall'applicazione funziona..ma non sempre come dovrebbe, per cui
temo sia un problema non di Access ma di ADO.

..non ho capito...
1) Da access funziona sempre?
2) da app funziona a volte si a volte no?
A.

problemi con ado, e piuttosto grossi,  c'erano con Delphi 5 poi era
stato rilasciato un servicepack che li aveva risolti, che versione di
Delphi stai usando?

 2) da app funziona a volte si a volte no?
Esatto, ed è la prima volta che mi capita.
Inizialmente ho pensato che fosse un problema di label, nel senso che
magari non riusciva ad aggiornare la caption ma anche con
application.processmessage non ha dato i risultati sperati. Ho creato
un file di log ed è risultato che il problema non è la label o altro
ma sta in qualche componente ADO (query o DataSource).


Dubito che possa rivelarsi lo stesso problema, o almeno me lo auguro.
Uso Delphi2010 + Win7 + Access2007.

 ma sta in qualche componente ADO (query o DataSource).
Posta il log affinchè noi possiamo giungere alle tue stesse conclusioni.
Mi rifiuto di credere che una banale SQL che effettua un recordcount non 
funzioni per colpa dei componenti ADO della VCL.
Dovresti fare un programmino di test, e provare a riprodurre il 
problema: ci riesci? Se riesci a riprodurre il problema allora mi 
rimangio le parole.


 funzioni per colpa dei componenti ADO della VCL.
Non dirlo a me, non mi sto affatto divertendo e anzi sto perdendo un
sacco di tempo.
Questo è il log che ho creato:
---------------------------Application
Start---------------------------
Updated Accettazioni	LabelValue:6	CountValue:6	Data:23/03/2011
Updated Carico		LabelValue:0	CountValue:0	Data:23/03/2011
Updated Consegne	LabelValue:0	CountValue:0	Data:23/03/2011
Updated Resi		LabelValue:0	CountValue:0	Data:23/03/2011
-----------------------------------------------------------------------
Sent Accettazioni	Size:63	Data:23/03/2011
Received Accettazioni	Size:63	Data:23/03/2011
Updated Accettazioni	LabelValue:6	CountValue:6	Data:23/03/2011
Le prime 4 righe vengono inserite all'esecuzione dell'applicazione e
labelValue si riferisce alla caption dopo l'open di ADOQuery,
CountValue invece è il valore recuperato dal COUNT SQL senza altri
giri; Sent Accettazioni significa che ho fatto un aggiornamento ai
dati del DB e che è partita la notifica per permettere al form di
aggiornare i valori delle label. Received Accettazioni sta ad indicare
che la parte di codice interessata ha ricevuto la notifica di
cambiamento nel DB. Per logica, se all'avvio dell'applicazione la
labelValue è 6, dopo aver inserito un dato nel DB questa cifra deve
essere superiore e invece non accade, 6 dati erano presenti all'avvio
e 6 risultano presenti dopo l'inserimento di uno nuovo. Inoltre, a
prova di questo, il CountValue risulta a 6 e non a 7 e questo implica
che ci sia qualcosa che non vada nell'ADO.
Il tutto viene eseguito in un FormMain che carica al suo interno
moduli package contenenti un frame. L'unico dubbio è se in qualche
modo possa rimanere freezato il DB dopo una Insert o altra operazione
per un breve periodo di tempo che potrebbe portare a questa anomalia
visto che dalla stessa applicazione accedo ad una tabella del DB con
due Query differenti nel giro di breve tempo (una che effettua
l'insert del nuovo dato e subito dopo un'altra che fa la select count).

 modo possa rimanere freezato il DB dopo una Insert o altra operazione
Avendo lavorato anche io per anni con il famigerato Access.. il tuo 
dubbio mi appare pi� che fondato... la concorrenza viene gestita alla 
meno peggio...


 che ci sia qualcosa che non vada nell'ADO.
Ascolta, prova a decorare il codice di inserimento dei dati nuovi in un 
bel transaction e committa.
cosa ti risulta?



Per scrupolo farò anche questo ma se l'insert non dovesse andare a
buon fine, i dati non ci sarebbero nemmeno sul DB e invece se
controllo direttamente da Access risultano presenti e inseriti
correttamente ma il COUNT per qualche strano motivo non se li fila
comunque.
Altra cosa che ho notato, è che non ci sono comportamenti fissi anche
se spesse volte la prima insert non viene rilevata se non dopo una
seconda insert; ma non è una regola fissa e anche successivamente,
ogni tanto l'anomalia si ripete.

 cosa ti risulta?
Ok, ho inserito transaction, commit e rollback ma non risultano nè
errori nè problemi di alcun genere e l'insert funziona perfettamente,
il Count SQL no. Esattamente come prima. Incredibile.

Sui DATABASE esistono delle impostazioni sulla visibilita' dei record
in uso da parte delle transazioni.
Ad esempio, il ng si riempi' di post di persone che segnalavano casini
del genere su interbase..
Tutto risolto mettendo una opzione "read committed" non mi ricordo
dove...
Magari anche quella latrina di access ha una impostazione del
genere...
A.


Una domanda per capire se ho capito:
passo 1) Lanci la query per avere il numero di record (es: 6)
passo 2) Fai degli inserimenti sul database (p. es: 2)
   (su access vedi i dati)
passo 3) Lanci la query per avere il numero di record:
   ALLARME!     non ottieni 8 ma 6
E' questo quello che succede?
Saluti
Paolo Arosio

 E' questo quello che succede?
Esatto, anche se in realtà il passo 1) avviene solo all'apertura
dell'applicazione e successivamente i passi possibili sono, in ordine
di esecuzione, solo il 2) e il 3) in sequenza.
Detto questo, visto che hai seguito attivamente anche la discussione
precedente sulla comunicazione delle notifiche, il problema che
riscontravo era proprio questo e non dipendeva nè dalle notifiche nè
dalla label che non si aggiornava correttamente nè dal copydata.

 genere...
ahahahaha alberto leggerti al mattino appena entrati in ufficio mi mette 
sempre di buon umore... :-D !!!!


 dalla label che non si aggiornava correttamente nè dal copydata.
Quindi stai dicendo che il thread precedente è chiuso, che 
l'architettura era fully working e che si trattava di questa 
inconsistenza nel database?


 di esecuzione, solo il 2) e il 3) in sequenza.
Quale meccanismo usi per lanciare la query al punto 3?
è collegata ad un after update di un altro dataset?
hai un pulsante refresh che attiva la query?
Prova a mettere un po' di istruzioni di trace / debug (chiamale come
vuoi) nel tuo codice.
Qualcosa tipo:
...// evento che attiva la query
Logger.Log('Before refreshing recordCount');
Logger.Log('recordCount is: ' + inttostr(MyDataModule.RecordCount));
MyDataModule.UpdateRecordCount;
Logger.Log('After refreshing recordCount, it is: ' +
inttostr(MyDataModule.RecordCount) );
Ho supposto che le adoquery siano in un DataModule che espone
UpdateRecordCount (la procedura che chiude e apre la query che fa il
conteggio) e RecordCount (dove il risultato è bufferizzato).
Se fra i componenti che usi non c'è un Logger, puoi facilmente crearne
uno minimale (codice scritto qui, quindi non testato):
uses Classes, Windows
TLogger = class(TObject)
public
  procedure Log(msg: String);
end;
procedure TLogger.Log(msg: String);
begin
  OutputDebugString(PChar(
     FormatDateTime('hhnnss.zzz', Now)+ #9 + msg));
end;
e ne fai un singleton.
In realtà non è proprio l'approccio migliore, dato che RecordCount
perde di validità non appena UpdateRecordCount è terminato: sarebbe
meglio avere il valore fresco, quindi RecordCount dovrebbe essere una
property read-only il cui metodo getter è la funzione che chiude e
apre la query "select count(<KeyCol>) from <table> where <condition>"
restituendo il risultato.
Hai ovviamente un overhead, ma "cchissenneffrega", purché il risultato
sia aggiornato.
Saluti
Paolo Arosio

 inconsistenza nel database?
Esatto.


Ho già fatto gran parte dei tuoi suggerimenti, in modi differenti ma
con lo stesso risultato.
In ogni caso cerco di fare un po di chiarezza anche se sto valutando
l'idea di modificare un po la struttura sperando di bypassare il
problema; non saranno modifiche consistenti per fortuna ma preferivo
utilizzare l'architettura attuale, poco male, anche se sto problema
sta diventando una questione di principio.
Comunque, qui un po di codice e relativa spiegazione.
1. avvio l'applicazione e nel costructor richiamo la funzione per
recuperare il count SQL e assegnarne il valore alla label. Questa è la
funzione che richiamo e che sarà sempre la medesima anche
successivamente (quindi il punto 3. di Paolo):
procedure TFormMain.AggiornaAccettazioni;
Var
  LogFile : TextFile;
begin
  ADOQueryAccettazioni.Active := false;
  ADOQueryAccettazioni.SQL.Clear;
  ADOQueryAccettazioni.SQL.Add('SELECT COUNT(ID) AS CountAccettazioni
FROM [t_tracer] WHERE [Inviato] = false');
  ADOQueryAccettazioni.Open;
  ADOQueryAccettazioni.Active := true;
  if ADOQueryAccettazioni.FieldByName('CountAccettazioni').AsInteger >
0 then
    BottoneInviaAccettazioni.Enabled := true
  else
    BottoneInviaAccettazioni.Enabled := false;
  LabelAccettazioni.Caption :=
ADOQueryAccettazioni.FieldByName('CountAccettazioni').AsString;
  AssignFile(LogFile,ExtractFilePath(Application.ExeName)+'Temp\Log'+
FormatDateTime('yyyymmdd', Date)+'.txt');
  if FileExists(ExtractFilePath(Application.ExeName)+'Temp\Log'+
FormatDateTime('yyyymmdd', Date)+'.txt') then
    Append(LogFile)
  else
    Rewrite(LogFile);
  Writeln(LogFile, 'Updated
Accettazioni'+#9+'LabelValue:'+LabelAccettazioni.Caption
+#9+'CountValue:'+ADOQueryAccettazioni.FieldByName('CountAccettazioni').AsString
+#9+'Data:'+DateToStr(Date)+' '+TimeToStr(Time()));
  Closefile(LogFile);
end;
2. Carico i vari package singolarmente in base alla scelta dell'utente
e alla relativa selezione del menu. Inserisco i nuovi dati nel DB e
invio la notifica di aggiornamento al formMain (evito di postare
codice sull'inserimento dei nuovi dati perchè è parecchio lungo). Qui
il sorgente di invio notifica e poi apro una piccola parentesi.
procedure TFrameNuovoTracer.SendData(
  copyDataStruct: TCopyDataStruct);
var
  receiverHandle  : THandle;
  res : integer;
begin
  //receiverHandle :=
FindWindow(PChar('TFormMain'),PChar('FormMain'));
  receiverHandle := Application.MainForm.Handle;
  if receiverHandle = 0 then
  begin
    ShowMessage('Data Receiver NOT found!');
    Exit;
  end;
  res := SendMessage(receiverHandle, WM_COPYDATA, Integer(Handle),
Integer(@copyDataStruct));
  if res <> 1 then ShowMessage('Errore durante la procedura di
aggiornamento dei controlli nel form principale.');
end;
procedure TFrameNuovoTracer.SendRecord();
var
  sampleRecord : TSampleRecord;
  copyDataStruct : TCopyDataStruct;
  LogFile : TextFile;
begin
  sampleRecord.s := 'Accettazioni';
  sampleRecord.i := 0;
  sampleRecord.d := Now;
  copyDataStruct.dwData := Integer(cdtRecord); //use it to identify
the message contents
  copyDataStruct.cbData := SizeOf(sampleRecord);
  copyDataStruct.lpData := @sampleRecord;
  AssignFile(LogFile,ExtractFilePath(Application.ExeName)+'Temp\Log'+
FormatDateTime('yyyymmdd', Date)+'.txt');
  if FileExists(ExtractFilePath(Application.ExeName)+'Temp\Log'+
FormatDateTime('yyyymmdd', Date)+'.txt') then
    Append(LogFile)
  else
    Rewrite(LogFile);
  Writeln(LogFile, 'Sent
Accettazioni'+#9+'Size:'+IntToStr(sizeof(sampleRecord))
+#9#9#9#9+'Data:'+DateToStr(Date)+' '+TimeToStr(Time()));
  Closefile(LogFile);
  SendData(copyDataStruct);
end;
Come vedete, la struttura del mio record è composta da 3 tipi di dati,
una stringa, un intero e una data. Quello che faccio ora è sfruttare
solo la stringa e utilizzarla come etichetta per sapere quale nuovo
dato è stato inserito nel DB per poi richiamare la funzione del punto
1. per fare il count SQL e assegnarne il valore alla relativa label;
l'alternativa è eseguire il count nei singoli package al posto della
funzione precedente e presente solo nel formMain per poi passare
attraverso la notifica sia la stringa come già avviene, sia l'intero
che andrà direttamente ad aggiornare la label. In parole povere sposto
la funzione count dal formMain ai vari package.
3. Una volta fatto il nuovo inserimento nel DB e inviato la notifica
al formMain, allo stato attuale richiamo nuovamente la funzione del
punto 1. che restituirà il nuovo Count e che andrà ad aggiornare la
label.
Sicuramente l'approccio migliore è quello attuale così evito di
ripetere N volte la stessa query per ogni singolo package.
Spero di essere stato chiaro.
Grazie

 dalla label che non si aggiornava correttamente nè dal copydata.
Beh, il copydata magari funzionava ma per grazia divina (per il fatto
che il tutto era in-process). Il man dice che non si devono mettere
dei puntatori nella struct passata.
Tra l'altro, l'msdn (
http://msdn.microsoft.com/en-us/library/
ms649011%28v=vs.85%29.aspx) dice "An application sends the WM_COPYDATA
message to pass data to another application."
Saluti
Paolo Arosio

Ragazzi, vi comunico che ho risolto in altro modo ma l'anomalia di
prima senza motivi apparenti è rimasta senza una risposta.
Praticamente, ho provato a gestire le notifiche tramite un observer
come suggerito dal buon Paolo che mi ha anche fornito un esempio da
cui partire (GRAZIE INFINITE per la pazienza e per l'aiuto
fondamentale), ho fatto un test per verificare che il problema non
fosse realmente imputabile al WM_COPYDATA e infatti non lo era, come
al solito ogni tanto mi saltava il Count (sospiro di solievo, vi avrei
aiutato a lapidarmi); a questo punto ho implementato il COUNT SQL
all'interno del package e tramite notifica nuova ho passato al
MainForm sia l'etichetta solita "Accettazioni", sia il COUNT SQL. Da
MainForm ho dovuto solo recuperare l'etichetta e associare il COUNT
alla relativa label.
Ho eseguito una decina di test e non ha perso un colpo (mentre prima
in media su 10 ne perdevo 2), non vorrei dirlo troppo forte ma sembra
che funzioni.
Direi che il problema è stato risolto anche se il dubbio sull'anomalia
rimane, per ora mi accontento di aver sistemato il bug e di poter
proseguire con lo sviluppo.
GRAZIE MILLE A TUTTI PER LA PAZIENZA E PER L'INTERESSAMENTO, GRAZIE.