Newsgroup: it.comp.lang.visual-basic
Titolo: [?] Implementazione look/unlook record db (lungo)

Salve a tutti, mi sono trovato ad implementare una gestione della semaforica per il blocco e sblocco
dei record di un db, ho sviluppato alcune routine seguendo un metodo che spero sia valido.
Mi piacerebbe sapere cosa ne pensate.
Vi spiego come ho deciso di gestire il tutto:
Dentro il db mi creo una tabella (tablook) dove andro' ad inserire i dati del blocco applicato alle
altre tabelle, la struttura di tablook e' la seguente:
idlook     CHAR(100)
tipo       CHAR(1)
tabella    CHAR(50)
record     CHAR(100)
utente     CHAR(50)
data       CHAR(8)
ora        CHAR(6)
al momento che dovro bloccare un record della tabella clienti, prima mi andro' avedere su tablook se
qualcuno a gia bloccato il record altrimenti lo blocco scrivendo su tablook i dati del blocco
applicato, ovvero:
idlook 	= chiave univoca che mi identifica il blocco record, la chiave viene formata nel seguente
modo, utente & data & ora & tipo & tabella & Trim$(Str$(lNumLook))
dove lNumLook e' un numero progressivo che viene aumentato ad ogni blocco applicato 
tipo	= tipo del blocco (per ora ho implementato soltanto il tipo R, blocco al livello di record)
tabella 	= nome della tabella su cui vene applicato il blocco ( es: clienti)
record	= chiave univoca del record bloccato converita in stringa (se la chiave di ricerca della
tabella clienti e' un numerico lo converto in stringa e lo metto dentro record)
utente	= nome utente che blocca il record (obbligatorio)
data	= data in cui viene applicato il blocco (di tipo stringa nella forma AAAAMMGG)
ora	= ora in cui viene applicato il blocco (di tipo stringa nella forma HHMMSS)
Le procedure implementete sono le seguenti:
Sub createTabLook(cntDb As ADODB.connection)
	Crea la tabella di gestione look/unlook nel db passato
Function lookRecord(cntDb As ADODB.connection, ByVal strTab As String, ByVal strRec As String) As
String
	Cerca di bloccate un record, sulla tabella strTab, con record la chi chiave e' strRec, 
	se il blocco ha successo ritorna il una stringa contenente la chiave del blocco altrimenti
	ritorna una stringa nulla
Function IsLookRecord(cntDb As ADODB.connection, ByVal strTab As String, ByVal strRec As String,
Optional ByRef strIDlook As String) As Long
	Controlla se il record con chiave strRec nella tabella strTab e' bloccato, se si ritorna il
	numero di blocchi applicati, in strIDlook viene restituito la chiave del primo blocco 
	trovato sul record
Function getInfoLook(cntDb As ADODB.connection, ByVal strIDlook As String, _
                            ByRef strTipo As String, _
                            ByRef strTabella As String, _
                            ByRef strRecord As String, _
                            ByRef strUtente As String, _
                            ByRef strData As String, _
                            ByRef strOra As String)
	Legge informazioni del blocco con chiave strIDlook e le mette nelle variabile passate
	per referenza.
Public Function unlook(cntDb As ADODB.connection, ByVal strIDlook As String) As Boolean
	Sblocca il record con chiave di blocco strIDlook
Qui sotto vi posto il codice delle procedure implementate, queste sono dentro una libreria il cui
nome e' lib
' *************************************************
' **************** Modulo lib *******************
' *************************************************
' *******************************************************************************
' Gestione della semaforica con blocco e sblocco record
' *******************************************************************************
' Se la tabella per la gestione del look/unlook dei
' record non esiste la crea
' -------------------------------------------------
' cntDB     = connessione del db ado su cui operare
Public Sub createTabLook(cntDb As ADODB.connection)
    Dim strSQL As String
 
    If Not searchTable(cntDb, "tablook") Then
 
        strSQL = "CREATE TABLE tablook (" & _
                        "idlook     CHAR(100), " & _
                        "tipo       CHAR(1), " & _
                        "tabella    CHAR(50), " & _
                        "record     CHAR(100), " & _
                        "utente     CHAR(50), " & _
                        "data       CHAR(8), " & _
                        "ora        CHAR(6) " & _
                    " )"
 
        cntDb.execute strSQL
 
 
        strSQL = "CREATE UNIQUE INDEX ndxTablook ON tablook ( idlook ) WITH PRIMARY"
        cntDb.execute strSQL
 
 
    End If
 
End Sub
' Elimina la tabella per la gestione del look/unlook
' dei record
' --------------------------------------------------
' cntDB     = connessione del db ado su cui operare
Public Sub dropTabLook(cntDb As ADODB.connection)
    If searchTable(cntDb, "tablook") Then cntDb.execute "DROP TABLE tablook"
End Sub
' Cerca di bloccare un record
' --------------------------------------------------
' cntDB         = connessione del db ado su cui operare
' strTab        = nome della tabella su cui bloccare il record
' strRec        = identificativo del record in formato stringa da bloccare
' --------------------------------------------------
' lookRecord    <> "" - Valore della chiave utilizzata per il blocco del record
'               =  "" - Non e' stato possibile bloccare il record
Public Function lookRecord(cntDb As ADODB.connection, ByVal strTab As String, ByVal strRec As
String) As String
    Dim strSQL As String
    Dim strIDlook As String * 100
    Dim strTipo As String * 1
    Dim strData As String * 8
    Dim strOra As String * 6
 
    Dim rstQuery As ADODB.recordset
 
    strTipo = "R"
    strData = date2str(Date)
    strOra = time2str(Now, True)
    lNumLook = lNumLook + 1: If lNumLook > 2000000000 Then lNumLook = 1
 
 
    ' Controlla se il record e' gia bloccato
 
    If lib.IsLookRecord(cntDb, strTab, strRec) > 0 Then     ' Il record e' gia bloccato
        lookRecord = ""                                     ' esce senza restituire nessun
        Exit Function                                       ' chiave di blocco
    End If
 
 
    ' Inserisce il blocco del record
 
    strIDlook = strUser & strData & strOra & strTipo & strTab & Trim$(Str$(lNumLook))
 
    strSQL = "INSERT INTO tablook " & _
                        "VALUES ( " & _
                            str2sql(strIDlook) & ", " & _
                            str2sql(strTipo) & ", " & _
                            str2sql(strTab) & ", " & _
                            str2sql(strRec) & ", " & _
                            str2sql(strUser) & ", " & _
                            str2sql(strData) & ", " & _
                            str2sql(strOra) & _
                        " ) "
 
    ' Se non riesce a scrivere il blocco del record esce
    On Error GoTo err_blocco_fallito
 
    cntDb.execute strSQL
 
    On Error GoTo 0
 
 
    ' Controlla se sono stati effettuati altri blocchi oltre a questo
 
    If lib.IsLookRecord(cntDb, strTab, strRec) > 1 Then     ' Il blocco e' stato effettuato da altri
processi
        Call lib.unlook(cntDb, strIDlook)                   ' quindi sblocca il record
precedentemente bloccato
        lookRecord = ""                                     ' esce senza restituire nessun
        Exit Function                                       ' chiave di blocco
    End If
 
    lookRecord = strIDlook
 
    Exit Function
err_blocco_fallito:
    lookRecord = ""
End Function
' Controlla se il record di una tabella e' bloccato
' --------------------------------------------------
' cntDB         = connessione del db ado su cui operare
' strTab        = nome della tabella su cui bloccare il record
' strRec        = identificativo del record in formato stringa da bloccare
' --------------------------------------------------
' IsLookRecord  > 0 - Numero di blocchi applicati sul record
'               = 0 - Il record non e' bloccato
' strIDlook     = Opzionale - Restituisce l'id del blocco applicato al record
'                             se in questo argomento viene passato un valore, la routine restituira'
'                             il l'id del blocco applicato al record che pero' non abbia l'id
'                             specificato nel parametro.
Public Function IsLookRecord(cntDb As ADODB.connection, ByVal strTab As String, ByVal strRec As
String, Optional ByRef strIDlook As String) As Long
    Dim strSQL As String
    Dim strIDlookRet As String
    Dim strTipo As String * 1
    Dim rstQuery As ADODB.recordset
 
    strTipo = "R"
 
    strSQL = "SELECT COUNT(*) AS totrec FROM tablook WHERE " & _
                        "tipo       = " & str2sql(strTipo) & " AND " & _
                        "tabella    = " & str2sql(strTab) & " AND " & _
                        "record     = " & str2sql(strRec)
 
    Set rstQuery = New ADODB.recordset
    rstQuery.Open strSQL, cntDb
 
    IsLookRecord = rstQuery.Fields!totrec
    rstQuery.Close
    Set rstQuery = Nothing
 
    ' Se e' stato specificato l'argomento strIDlook ricerca il primo blocco applicato che non sia
    ' quello passato in strIDlook e lo restituisce in questa variabile per passarlo alla routine
chiamante
    If IsLookRecord > 0 Then
 
        strSQL = "SELECT * FROM tablook WHERE " & _
                        "tipo       = " & str2sql(strTipo) & " AND " & _
                        "tabella    = " & str2sql(strTab) & " AND " & _
                        "record     = " & str2sql(strRec)
 
        Set rstQuery = New ADODB.recordset
        rstQuery.Open strSQL, cntDb, adOpenForwardOnly
 
        strIDlookRet = ""
        Do Until rstQuery.EOF
            If (Len(strIDlook) > 0 And Trim$(rstQuery.Fields!idLook) <> strIDlook) Or Len(strIDlook)
= 0 Then
                strIDlookRet = rstQuery.Fields!idLook
                Exit Do
            End If
        Loop
        strIDlook = strIDlookRet
 
        rstQuery.Close
        Set rstQuery = Nothing
    End If
 
End Function
' Legge informazioni su un blocco
' -------------------------
' cntDb         = connessione del db ado su cui operare
' strIdLook     = identificativo del blocco da cui prendere informazioni
' -------------------------
' strTipo         Variabili passate per referenza su cui verranno
' strTabella      restituiti i valori dei campi del record che identifica
' strRecord       il blocco passato con l'id strIDlook
' strUtente
' strData
' strOra
'  ------------------------
Public Function getInfoLook(cntDb As ADODB.connection, ByVal strIDlook As String, _
                            ByRef strTipo As String, _
                            ByRef strTabella As String, _
                            ByRef strRecord As String, _
                            ByRef strUtente As String, _
                            ByRef strData As String, _
                            ByRef strOra As String)
    Dim nrec As Long
    Dim strSQL As String
    Dim rstQuery As ADODB.recordset
 
    strTipo = "": strTabella = "": strRecord = "": strUtente = "": strData = "": strOra = ""
 
    If Len(strIDlook) = 0 Then Exit Function
 
    strSQL = "SELECT * FROM tablook WHERE idlook = " & str2sql(strIDlook)
 
    Set rstQuery = New ADODB.recordset
    rstQuery.Open strSQL, cntDb
 
    If Not rstQuery.EOF Then
        strTipo = Trim$(rstQuery.Fields!tipo)
        strTabella = Trim$(rstQuery.Fields!tabella)
        strRecord = Trim$(rstQuery.Fields!Record)
        strUtente = Trim$(rstQuery.Fields!utente)
        strData = Trim$(rstQuery.Fields!Data)
        strOra = Trim$(rstQuery.Fields!ora)
    End If
 
    rstQuery.Close
    Set rstQuery = Nothing
End Function
 
' Toglie un blocco avente l'identificativo passato alla funzione
' -------------------------
' cntDb         = connessione del db ado su cui operare
' strIdLook     = identificativo del blocco da togliere
' -------------------------
' unlook        = true  - sblocco avvenuto con successo
'                 false - sblocco non riuscito
Public Function unlook(cntDb As ADODB.connection, ByVal strIDlook As String) As Boolean
    Dim nrec As Long
    Dim cmdSql As ADODB.Command
 
    If Len(strIDlook) = 0 Then unlook = False: Exit Function
 
    Set cmdSql = New ADODB.Command
 
    cmdSql.ActiveConnection = cntDb
    cmdSql.CommandText = "DELETE FROM tablook WHERE idlook = " & str2sql(strIDlook)
    cmdSql.execute nrec
 
    If nrec = 0 Then unlook = False Else unlook = True
End Function

 ha scritto nel messaggio
news:3c1692f...@news.tin.it...
< tutto un cut >
Cosě a naso dovrebbe funzionare; ricordati anche di gestire la concorrenza
di accesso alla tabella di lock;
io eliminerei perň quell'identificativo di lock in quanto la sua generazione
č poco affidabile. Lasciala fare ad un contatore Access oppure ad un
UniqueIdentifier di SQLServer.
PS : Look = guardare; Lock = blocco.
Saluti

PS : Look = guardare; Lock = blocco.
Hai ragione, una svista colossale :-))
Ciao, David.

 altre tabelle, la struttura di tablook e' la seguente:
[...]
mmmmmmmm. Esempio:
CASO 1
tempo t0: il processo A che vuole mettere un lock sil record 3 della tabella
clienti controlla se esiste già un lock, e non lo
               trova
tempo t1: il processo B che vuole mettere un lock sil record 3 della tabella
clienti controlla se esiste già un lock, e non lo
               trova
tempo t2: il processo A crea il lock
tempo t3: il processo B crea il lock
e tutti e due pensano di avere accesso esclusivo al record.
CASO 2
il processo A blocca il record 3 della tabella clienti, e poi va in crash.
Da quel momento in poi nessuno può più accedervi.
E questo è uno dei motivi fondamentali per cui i lock _non_ si gestiscono
con tabelle del db.
Sai perchè i lock li si fa gestire al db? Perchè ti garantisce che la
lettura dello stato del lock e la sua eventuale creazione sono un'operazione
atomica, e cioè un'operazione che non può essere interrotta da un context
switch.
Questo vale per i db client/server, perchè il db si trova tutto in uno
stesso spazio di memoria, e può utilizzare i meccanismi semaforici del
sistema operativo, ed è di gran lunga il sistema migliore.
Per i db come access, che condividono soltanto il file, la questione è più
complicata, e comunque sostanzialmente si basa sulla condivisione di un file
(il famoso .ldb), che sfrutta i meccanismi di locking della rete (questi sì
affidabili), ed alcune proprietà particolari tipo che si può bloccare un
byte di un file anche se va oltre la fine del file, per garantire
un'assegnazione _centralizzata_ dei lock. questo metodo, detto tra noi, è
una schifezza, ma tant'è, Access non ha mai preteso di essere un db di rete.
Hth
Alfonso

sistema operativo, ed è di gran lunga il sistema migliore.
Diversi giorni fa ho seguito alcune discussioni qui sul ng dove si parlava di qual'era il modo
migliore per fare gestire il lock/unlock se come dici tu ora (sicuramente migliore, pero' forse meno
portabile su db di diverso tipo) e quello che ho sviluppato io (sicuramente implementabile su
qualsiasi db).
L'unico modo con con cui ado gestisce il blocco dei record se non sbaglio avviene tramite i tipi di
cursori utilizzati per accedere ai dati. Il record va in look solo al momento in cui il cursore vi
e' sopra o al momento in cui viene aggiornato (la mia conoscenza su questo argomento e' praticamente
nulla). Mettiamo nel mio prog. io mi prendo i dati del record con un select, chiudo il recordset,
vario i dati che ho in memori e modifico il record con un update sql, l'unico modo con cui posso
gestirmi il lock/unlock dei record e' quello da me intrapreso (credo).
Ciao, David.

 gestirmi il lock/unlock dei record e' quello da me intrapreso (credo).
Credi male:). Puoi utilizzare sia l'isolation level (si configura con la
connessione), che forzare il tipo di lock nella singola istruzione
UPDATE ... WITH (tipo di lock)
anche se questo, come ti dicevo, è fortemente sconsigliato.
ciao
Alfonso