Razvijanje aplikacija u Accessu
azvijanje Accessovih aplikacija za višekorisničko okruženje zahteva dodatno planiranje i drugačiji način razmišljanja u poređenju sa jednokorisničkim aplikacijama, ali ga nije teško naučiti. U ovom poglavlju istražujemo mogućnosti Accessa za višekorisničku podršku i najbolje načine u Accessu 2000 za planiranje i projektovanje višekorisničkih sistema za rad s bazama podataka.
Podaci se u Accessovim aplikacijama mogu deliti na tri načina:
Korišćenjem servera za datoteke U ovom scenariju, na svakoj radnoj stanici radi po jedan primerak mašine Jet, koji šalje zahteve bazi podataka na centralnom serveru.
Korišćenjem replikovanja U ovom scenariju, replikovane kopije, ili replike, baze podataka distribuiraju se širom organizacije. Podaci se dele između replika njihovim sinhronizovanjem u redovnim vremenskim razmacima.
Korišćenjem klijent/server sistema U ovom scenariju Access se koristi kao čeona komponenta koja sarađuje sa serverom sistema za upravljanje bazama podataka koji podržava SQL naredbe, kao što su SQL Server, Microsoft Data Engine (MSDE) ili Oracle. Svim podacima upravlja server baze podataka.
U ovom poglavlju govorićemo o tome kako više korisnika može da deli iste podatke pristupanjem serveru za datoteke kojim upravlja mašina baze podataka Jet. Replikovanje je detaljnije objašnjeno u poglavlju 9. Pristup podacima u klijent/server sistemima obrađen je u više poglavlja, počev od poglavlja 3.
Ako drugačije ne zadate, Access smešta sve svoje objekte u istu .MDB datoteku. To loše utiče na performanse u višekorisničkom okruženju zato što mašina Jet mora putem mreže da pošalje objekat korisniku kad god vaša aplikacija treba da upotrebi neki od objekata (npr. obrazac). U produkcionom okruženju, u kome se ništa ne menja osim podataka, suvišan je veći deo saobraćaja u mreži koji na ovaj način nastaje.
Suvišan saobraćaj u mreži možete da eliminišete razdvajanjem baze podataka na dva dela: na "aplikacionu" bazu podataka i na bazu podataka "za podatke". Na server za datoteke instalirajte ovu drugu bazu podataka (koja sadrži samo tabele), a aplikacionu bazu podataka (koja sadrži sve druge objekte) kopirajte na svaku radnu stanicu. Svakoj kopiji aplikacione baze podataka komandom File Get External Tables Link Tables pridružite tabele iz baze podataka "za podatke".
Ovaj pristup pruža sledeće prednosti:
Glavni nedostatak ovog pristupa jeste to što putanje ka pridruženim tabelama Access upisuje u obliku nepromenljivih podataka. To znači da, ukoliko premestite bazu podataka "za podatke", morate da obnovite veze sa pridruženim tabelama.
savet |
U Access je ugrađen dodatak Database Splitter koji pojednostavljuje deljenje baze podataka na dve baze, jednu za podatke i jednu za aplikaciju. |
|
Pošto Access čuva apsolutne putanje ka pridruženim tabelama, korišćenje tih tabela zahteva dodatno održavanje. Kada premestite bazu podataka "za podatke", prekinute veze možete ponovo da uspostavite na jedan od sledećih načina:
Baza podataka koja je pridružena drugom poglavlju, CH02APP.MDB, sadrži modul basLinkedTables koji sadrži kôd za upravljanje pridruženim tabelama. Ulazna tačka u taj deo koda je funkcija adhVerifyLinks, koja je prikazana u listingu 2.1. (Ova funkcija se pri pokretanju baze podataka poziva iz funkcije AutoExec, koja se, pak, poziva iz makroa AutoExec baze podataka.)
napomena |
Ukoliko želite da funkciju adhVerifyLinks koristite u svojim aplikacijama, treba da uvezete tri modula: basLinkedTables, basFileOpen i CommonDialog. |
|
Function adhVerifyLinks(strDataDatabase As String, _
strSampleTable As String) As Boolean
' Proverava stanje veza sa pridruženim tabelama.
' Ako otkrije prekinutu vezu, prvo pretražuje tekući direktorijum.
' Ako u njemu ne nađe traženu bazu podataka, korisniku prikazuje
' okvir za dijalog za otvaranje datoteka.
' Polazna pretpostavka: sve veze vode ka istoj ciljnoj MDB datoteci.
On Error GoTo adhVerifyLinksErr
strProcName = "adhVerifyLinks"
' Ispitujemo stanje veze sa tabelom čije je ime zadato
varReturn = CheckLink(strSampleTable)
' Učitavamo ime direktorijuma u kome se nalazi aplikaciona baza
strDBDir = CurrentProject.Path & "\"
' Ime baze podataka koja sadrži podatke zadato je
If (Dir$(strDBDir & strDataDatabase) <> "") Then
' Baza podataka za podatke pronađena je u tekućem direktorijumu
varFileName = strDBDir & strDataDatabase
' Korisnik će pomoću okvira za dijalog sam pronaći
strMsg = "Neophodna datoteka '" & _
" Pomoću sledećeg okvira za dijalog" & _
" možete pokušati da je pronađete na svom računaru." & _
" Ukoliko ne možete da je pronađete ili" & _
" niste sigurni šta treba da uradite, na sledećem " & _
" ekranu izaberite CANCEL i pozovite" & _
" administratora baze podataka."
MsgBox strMsg, vbOKOnly + vbCritical, strProcName
' Prikazujemo okvir za dijalog Open File pozivanjem funkcije
' adhCommonFileOpenSave iz modula basFileOpen.
strFilter = adhAddFilterItem( _
strFilter, "Access (*.mdb)", "*.mdb")
varFileName = adhCommonFileOpenSave( _
Flags:=cdlOFNHideReadOnly + cdlOFNNoChangeDir, _
DialogTitle:="Pronalaženje datoteke baze podataka")
' Korisnik je pritisnuo Cancel.
strMsg = "Ovu bazu podataka ne možete da koristite " & _
"dok ne pronađete datoteku '" & strDataDatabase & "'."
vbOKOnly + vbCritical, strProcName
varFileName = adhTrimNull(varFileName)
'Ponovno uspostavljanje veza. Najpre utvrđujemo ukupan broj tabela.
intNumTables = db.TableDefs.Count
Set cnn = CurrentProject.Connection
intNumTables = cat.Tables.Count
varReturn = SysCmd(acSysCmdInitMeter, _
"Uspostavljam veze s tabelama", intNumTables)
' Petlja za proveravanje svih tabela. Ponovo se povezuju
' samo one čije svojstvo Connect ne sadrži prazan znakovni niz.
' Ako je vrednost svojstva Connect prazan niz,
' onda to nije pridružena tabela.
tdf.Connect = ";DATABASE=" & varFileName
' Pošto pri izvršavanju metode RefreshLink može da
' nastane greška ukoliko nova putanja nije u redu,
' grešku obrađujemo u sledećem redu.
'Ako je veza prekinuta, funkcija daje povratnu vrednost
varReturn = SysCmd(acSysCmdUpdateMeter, intI + 1)
' Ako je svojstvo Type = "LINK", radi se o pridruženoj tabeli.
' U sledećem redu veza se ponovo uspostavlja i osvežava.
' Ukoliko nova putanja nije u redu, nastaje greška koju
tbl.Properties("Jet OLEDB:Link Datasource") = _
'Ako je veza prekinuta, funkcija daje povratnu vrednost
varReturn = SysCmd(acSysCmdUpdateMeter, intI + 1)
varReturn = SysCmd(acSysCmdRemoveMeter)
Err.Raise Err.Number, Err.Source, _
Err.Description, Err.HelpFile, Err.HelpContext
Funkcija adhVerifyLinks prihvata dva parametra: strDataDatabase, koji sadrži ime baze podataka sa podacima u koju su smeštene pridružene tabele, i strSampleTable, koji sadrži ime jedne od pridruženih tabela. Funkcija adhVerifyLinks počinje tako što proverava stanje veze sa zadatom tabelom. Pretpostavlja se da, ukoliko je ta veza u redu, onda to važi i za veze sa svim ostalim tabelama. (Ako želite, možete da izmenite kôd tako da proverava integritet svake veze pojedinačno.) Funkcija proverava stanje veze pozivanjem privatne funkcije CheckLink, koja je prikazana u listingu 2.2.
Private Function CheckLink(strTable As String) As Boolean
' Proverava stanje veze sa zadatom tabelom.
' (Zapravo, daje False i kada tabela ne postoji.)
' Ako ne možemo da utvrdimo ime prvog polja tabele,
' veza je verovatno prekinuta.
varRet = CurrentDb.TableDefs(strTable).Fields(0).Name
Set cnn = CurrentProject.Connection
' Metodom OpenSchema objekat tipa Recordset popunjavamo
' podacima o kolonama tabele zadate u parametru strTable.
' Ako je skup podataka prazan, to znači da Jet nije mogao
' da pronađe tabelu jer je veza verovatno prekinuta.
Set rst = cnn.OpenSchema(adSchemaColumns, _
Array(Empty, Empty, strTable, Empty))
Kada koristite model DAO, funkcija CheckLink proverava ispravnost veze tako što pokušava da učita ime prvog polja tabele. Ako se ta operacija završi uspehom, veza je ispravna; u suprotnom, smatra se da je veza prekinuta, pa funkcija daje False kao povratnu vrednost. Kada koristite model ADO, funkcija proverava ispravnost veze pozivanjem posebne metode OpenSchema objekta Connection. Metoda OpenSchema popunjava objekat tipa Recordset raznim podacima o šemi baze podataka. (Na ovaj način se brže utvrđuje da li je veza s tabelom prekinuta, nego kada se koristi ADOX objekat tipa Table.)
Ako funkcija CheckLink kao povratnu vrednost daje False, onda funkcija adhVerifyLinks, traži bazu podataka "za podatke" u istom direktorijumu u kome se nalazi i aplikaciona baza podataka. Ako je nađe, funkcija ponovo uspostavlja veze s njenim tabelama. Ako se baza podataka "za podatke" ne nalazi u istom direktorijumu kao aplikacija, funkcija prikazuje korisniku standardni Windowsov okvir za dijalog Open File.
Kada je baza locirana, funkcija adhVerifyLinks pokušava da ponovo uspostavi veze s pridruženim tabelama u toj bazi podataka. Kada se koristi model DAO, funkcija utvrđuje da li je određena tabela pridruženog tipa tako što najpre proverava da li svojstvo Connect objekta TableDef sadrži znakovni niz koji nije prazan. Ako nije prazan, funkcija ponovo uspostavlja vezu s tabelom tako što menja vrednost njenog svojstva Connect, a zatim poziva metodu RefreshLinks, kao u sledećem delu koda funkcije adhVerifyLinks:
tdf.Connect = ";DATABASE=" & varImeDatoteke
Kada se koristi model ADO, funkcija utvrđuje da li je određena tabela pridruženog tipa tako što najpre proverava da li svojstvo Type objekta Table sadrži vrednost "LINK". Ako je tako, funkcija ponovo uspostavlja vezu s tabelom tako što koristi svojstvo ADOX objekta Table, koje je specifično za dobavljača Jet podataka, kao u sledećem delu koda funkcije adhVerifyLinks:
Kada jedinstvenu bazu podataka postojeće aplikacije razdvojite na bazu podataka "za podatke" i na aplikacionu bazu podataka, vrlo je verovatno da ćete morati da izmenite određene delove VBA koda. Kada radite s pridruženim tabelama, ne možete direktno da koristite skupove podataka tipa Table (tabela), niti metodu Seek, ali umesto njih možete da koristite neku od sledećih alternativnih strategija:
Koju god metodu da odaberete, verovatno ćete morati da unesete određene izmene u kôd aplikacije. Međutim, moguće je napisati kôd koji koristi metodu Seek, bez obzira na to da li je tabela lokalna ili pridružena, što je prikazano na primeru koda u listingu 2.3 (DAO) i listingu 2.4 (ADO). Oba primera su iz baze podataka CH02APP.MDB.
Sub SeekLocalOrLinkedDAO(ByVal strTable As String, _
Optional ByVal strIndex As String = "PrimaryKey")
' Izvršava DAO Seek metodu nad tabelom koristeći
' zadati indeks i uslove pretraživanja. Radi i sa lokalnim
' i sa pridruženim Accessovim tabelama.
' strCompare: Niz vrednosti koje treba pronaći, razdvojene
' zarezima
' strIndex: Ime indeksa. Podrazumeva se "PrimaryKey"
' U prozoru Immediate ispisuje listu vrednosti polja
' ili poruku ' Zadata vrednost nije pronađena '.
' Učitavamo vrednost vezivnog niza iz definicije tabele
strConnect = db.TableDefs(strTable).Connect
' Ako je vezivni niz jednak "", radi se o lokalnoj tabeli.
' U suprotnom, izdvajamo iz vezivnog niza deo koji
intDBStart = InStr(strConnect, adhcDB)
intDBEnd = InStr(intDBStart + Len(adhcDB), _
If intDBEnd = 0 Then intDBEnd = Len(strConnect) + 1
strDB = Mid(strConnect, intDBStart + Len(adhcDB), _
' Otvaramo spoljnu bazu podataka.
Set db = DBEngine.Workspaces(0).OpenDatabase(strDB)
' Da bismo mogli da koristimo metodu Seek,
' treba da otvorimo Recordset objekat tipa Table.
Set rst = db.OpenRecordset(strTable, dbOpenTable)
' Ovaj primer samo ispisuje u prozoru Immediate
' pojedinačne vrednosti polja pronađenog zapisa,
' ali je to dovoljno da shvatite princip...
Debug.Print fld.Name & ": " & fld.Value
Sub SeekLocalOrLinkedADO(ByVal strTable As String, _
ByVal varCompare As Variant, _
Optional ByVal strIndex As String = "PrimaryKey")
' Izvršava ADO Seek metodu nad tabelom koristeći
' zadati indeks i uslove pretraživanja. Radi i sa lokalnim
' i sa pridruženim Accessovim tabelama.
' varCompare: Niz vrednosti koje treba pronaći
' strIndex: Ime indeksa. Podrazumeva se "PrimaryKey"
' U prozoru Immediate ispisuje listu vrednosti polja
' ili poruku ' Zadata vrednost nije pronađena '.
Set cnn = CurrentProject.Connection
' Ako je ovo pridružena tabela, strDB će sadržati
' ime izvorne baze podataka; u suprotnom,
' strDB će sadržati prazan znakovni niz.
strDB = cat.Tables(strTable). _
Properties("Jet OLEDB:Link Datasource")
' Uspostavljamo vezu sa spoljnom bazom podataka.
Set cnn = New ADODB.Connection
cnn.Open "Provider=Microsoft.Jet.OLEDB.4.0;" & _
' Da bismo mogli da koristimo metodu Seek,
' treba da otvorimo Recordset objekat tipa Table.
rst.Open strTable, cnn, adOpenKeyset, _
adLockOptimistic, adCmdTableDirect
rst.Seek varCompare, adSeekFirstEQ
' Ako tražena vrednost nije pronađena,
' svojtsvo EOF ima vrednost True.
' Ovaj primer samo ispisuje u prozoru Immediate
' pojedinačne vrednosti polja pronađenog zapisa,
' ali je to dovoljno da shvatite princip...
Debug.Print fld.Name & ": " & fld.Value
Debug.Print "Zadata vrednost nije pronađena."
Ova procedura radi tako što najpre iz šeme tabele učitava ime baze podataka "za podatke". DAO verzija ove funkcije (SeekLocalOrLinkedDAO, listing 2.3) izdvaja taj podatak iz stavke DATABASE svojstva Connect objekta TableDef. ADO verzija (SeekLocalOrLinkedADO, listing 2.4) jednostavnija je jer ne morate da izdvajate ime baze podataka iz vrednosti svojstva Jet OLEDB: Link Datasource ADOX objekta tipa Table, jer to svojstvo sadrži samo ime baze podataka.
U Accessu postoji više opcija i parametara koji utiču na ponašanje aplikacija u višekorisničkom okruženju, što je opisano u narednim odeljcima.
Režim rada u kome će Access otvoriti bazu podataka možete zadati na tri načina:
Interval osvežavanja sadržaja podataka možete da podesite na kartici Advanced okvira za dijalog Options. Na kraju svakog intervala koji zadate u svojstvu Refresh Interval, Access automatski proverava u otvorenim skupovima podataka i u tabelarnim prikazima da li je došlo do izmena. Standardni interval osvežavanja je 60 sekundi, što za neke aplikacije može da bude presporo. Međutim, ukoliko interval osvežavanja postavite na prenisku vrednost, možete da generišete preveliki saobraćaj u mreži. Da biste utvrdili koja vrednost odgovara vašoj aplikaciji, možda ćete morati malo da eksperimentišete. Opšte pravilo glasi da što je mreža manja, manji može da bude i interval osvežavanja bez štetnih posledica po saobraćaj u mreži.
Trenutak osvežavanja možete da zadate i svom VBA kodu. Da biste osvežili sadržaj tekućeg zapisa koji je prikazan na obrascu, upišite:
DAO i ADO Recordset objekti nemaju metode Refresh; međutim, možete da zahtevate "prisilno" osvežavanje sadržaja tekućeg zapisa izjednačavanjem svojstva Bookmark skupa podataka sa samim sobom, na ovaj način:
Da biste ponovo DAO ili ADO skup podataka popunili podacima, zadajte sledeću komandu:
Osvežavanje sadržaja tekućeg zapisa - automatsko, koje Access obavlja na kraju svakog intervala osvežavanja, ili ručno, pozivanjem metode Refresh - brže je od izvršavanja metode Requery radi ponovnog formiranja skupa podataka. Međutim, novi zapisi koje su drugi korisnici dodali pojavljuju se u skupu podataka tek pošto pozovete metodu Requery, dok zapisi koje su drugi korisnici izbrisali nestaju iz skupa podataka takođe tek kada pozovete metodu Requery (osim ako koristite ADO dinamički skup podataka).
Da bi više korisnika moglo da istovremeno pristupa istom zapisu, mašina baze podataka Jet zaključava (blokira) i otključava zapise. Pre Accessa 2000, Jet je zaključavao stranice zapisa, a ne pojedine zapise. Veličina stranice bila je 2 kilobajta (2048 bajta). To znači da je Jet najčešće zaključavao više od jednog zapisa. Koliko njihđ To je zavisilo od toga koliko je zapisa Jet mogao da uklopi u stranicu od 2048 bajta. U zavisnosti od veličine zapisa, to je moglo da bude bilo šta u opsegu od jednog do 30 zapisa.
Jedna od najznačajnijih novina Accessa 2000 jeste podrška za pravo zaključavanje na nivou zapisa! To ćete omogućiti ako u okviru za dijalog Options, na kartici Advanced, potvrdite polje Open Databases Using Record-Level Locking (slika 2.1). Zapravo, uključivanje/isključivanje ovog polja omogućava zaključavanje na nivou zapisa/stranice. Za većinu (ali ne i sve) operacije nad podacima ovaj parametar znači pravo zaključavanje na nivou zapisa (izuzeci su navedeni u narednom odeljku). S druge strane, ako ispred polja Open Databases Using Record-Level Locking uklonite znak potvrde, Jet će zaključavati samo cele stranice zapisa. Standardno važi zaključavanje na nivou pojedinačnog zapisa.
Nivo zaključavanja određuje prvi korisnik koji otvori bazu podataka. Pošto to prvi korisnik uradi, više ne možete da menjate nivo zaključavanja dok se svi korisnici ne ođave iz baze podataka.
napomena |
Veličina stranice je u Accessu 2000/Jetu 4.0 povećana sa 2 na 4 kilobajta. To je bilo neophodno da bi se obezbedila podrška za Unicode. |
|
Kada zadate zaključavanje zapisa/stranica, u nekim operacijama Jet zaključava pojedine zapise, dok u drugim zaključava cele stranice.
Jet primenjuje zaključavanje na nivou zapisa kada se za učitavanje/upisivanje podataka koriste:
Jet primenjuje zaključavanje na nivou stranica u sledećim slučajevima učitavanja/upisivanja podataka:
Kada u bazi podataka zadate zaključavanje na nivou stranica, Jet uvek zaključava samo stranice zapisa. To je isto ponašanje kao u prethodnim verzijama mašine Jet.
Zaključavanje na nivou zapisa obezbeđuje znatno bolje mogućnosti višekorisničkog pristupa podacima nego zaključavanje na nivou stranice. To znači da, kada se primenjuje zaključavanje na nivou zapisa, više korisnika može da istovremeno ažurira zapise u istoj tabeli. Međutim, to ne znači uvek i bolje performanse. Kada se istovremeno ažurira veliki broj zapisa, zaključavanje na nivou stranice može da obezbedi bolje performanse od zaključavanja na nivou zapisa.
Osim izbora odgovarajućeg nivoa zaključavanja (na nivou zapisa ili na nivou stranice), možete da zadate i trenutak zaključavanja, odnosno kada Jet treba da zaključa podatke. Skupove podataka možete da otvarate u jednom od sledećih režima rada (svaki od njih detaljnije je opisan u narednim odeljcima):
No Locks Ovaj režim se često naziva optimističko zaključavanje (engl. optimistic locking). Ako zadate No Locks, zapis (ili stranica koja sadrži zapis što se trenutno ažurira, ukoliko se primenjuje zaključavanje na nivou stranice) zaključan je samo u trenutku upisivanja u bazu podataka, ali ne i dok ga korisnik ažurira.
Edited Record È;im korisnik počne da ažurira sadržaj zapisa, on (ili stranica koja sadrži zapis koji se trenutno ažurira, ukoliko se primenjuje zaključavanje na nivou stranice) zaključava se i ostaje zaključan dok izmene ne budu snimljene u bazu podataka. To je poznato i kao pesimističko zaključavanje (engl. pesimistic locking).
All Records Ova vrednost čini da svi zapisi u celom skupu zapisa postanu zaključani. Ova opcija nije naročito korisna, osim za grupna ažuriranja ili za administrativno održavanje tabela.
Opcije koje određuju način zaključavanja objekata baze podataka, koji rade sa skupovima podataka, možete podešavati. Tabela 2.1 prikazuje koje su sve opcije zaključavanja na raspolaganju za pojedine vrste objekata baze podataka, kao i trenutak kada se zapisi zaključavaju. Parameter RecordLocks većine ovih objekata nasleđuje vrednost od opcije Default Record Locking, koja se zadaje na kartici Advanced okvira za dijalog Options (slika 2.1).
Optimističko zaključavanje (vrednost svojstva RecordLocks je No Locks) omogućava da više korisnika istovremeno ažurira iste zapise uz manji broj sukoba oko zaključavanja zapisa. Međutim, time se povećava rizik nastajanja sukoba pri upisivanju podataka. Sukob pri upisivanju nastaje kada:
Kada primenjujete pesimističko zaključavanje (svojstvo RecordLocks = Edited Record), u svakom datom trenutku samo jedan korisnik može da menja sadržaj zapisa. To je veliki problem kada primenjujete zaključavanje na nivou stranice, jer u svakom trenutku samo jedan korisnik može da ažurira bilo koji zapis koji se nalazi na zaključanoj stranici.
U većini slučajeva ne biste želeli da dva korisnika menjaju sadržaj istog zapisa u isto vreme. Na osnovu ove činjenice zaključili biste da treba da primenjujte pesimističko zaključavanje. Međutim, u verzijama Accessa pre Accessa 2000 to je značilo zaključavanje cele stranice zapisa, a ne samo određenog zapisa, što je često bilo neprihvatljivo. Zbog toga smo u prethodnim izdanjima ove knjige preporučivali optimističko zaključavanje, osim u aplikacijama u kojima bi sukobi pri upisivanju bili neprihvatljivi. Međutim, pošto sada Access 2000 omogućava zaključavanje pojedinačnih zapisa, smatramo da je pesimističko zaključavanje bolji izbor za većinu aplikacija. Ipak, ponekad ćete naići i na slučajeve u kojima bi trebalo da razmotrite i upotrebu optimističkog zaključavanja. Na primer, ako imate korisnike koji imaju običaj da zapise zaključavaju duže vreme, može biti pogodnije da se opredelite za optimističko zaključavanje. Tu vrstu zaključavanja možete da primenjujete na zaključavanje stranica, jer, na primer, vaša aplikacija sadrži veze ka bazi podataka u formatu Accessa 97.
savet |
Ukoliko postoji mogućnost zaključavanja zapisa, preporučujemo vam da primenjujete pesimističko zaključavanje u većini Access 2000 aplikacija koje koriste mašinu baze podataka Jet 4. |
|
Kada koristite vezane obrasce, Access se automatski stara o zaključavanju zapisa. U narednih nekoliko odeljaka opisano je kako se to odvija i kako možete da izmenite standardno ponašanje Accessa kada se radi o zaključavanju.
Kao što je već bilo pomenuto u prethodnom delu ovoga poglavlja, najozbiljniji problem sa optimističkim zaključavanjem jeste mogućnost nastajanja sukoba pri upisivanju. Kada u vezanom obrascu dođe do takvog sukoba, Access prikazuje okvir za dijalog Write Conflict, kao u primeru na slici 2.2 gde je prikazan sukob na obrascu frmCustomerOptimistic1 iz baze podataka CH02APP.MDB.
Ovaj okvir za dijalog nudi korisniku tri mogućnosti:
Save Record Ako se korisnik opredeli za ovu opciju, izmene koje je uneo poništavaju izmene koje je drugi korisnik uneo. Zbog toga u većini slučajeva treba izbegavati ovu opciju; ona "naslepo" odbacuje izmene koje je uneo drugi korisnik.
Copy to Clipboard Ova opcija kopira na Clipboard izmene koje je tekući korisnik uneo i osvežava sadržaj zapisa izmenama koje je uneo drugi korisnik. To je dobar izbor za korisnike koji shvataju u čemu je problem, ali zahteva znanje kojim prosečan korisnik ne raspolaže. Ako ste koristili ovu opciju još u vreme Accessa 2.0, drago nam je što možemo da vas obavestimo da je ispravljena greška koja je sprečavala ponovno umetanje formatiranih podataka u obrazac.
Drop Changes Kada korisnik izabere ovu opciju, odbacuju se izmene koje je on uneo, a zapis se ažurira izmenama koje je uneo drugi korisnik.
U Accessu 2000 postoji greška (koja je tu još od vremena Accessa 2 - neke od njih se zaista sporo otklanjaju) koja se pojavljuje kada između dve tabele imate vezu tipa "jedan prema više", a u kojoj je isključeno lančano ažuriranje, pa u okviru za dijalog Write Conflict izaberete opciju Save Record za zapis koji se nalazi na strani "jedan" veze. U takvim slučajevima pojavljuju se pogrešne poruke o narušavanju referencijalnog integriteta čak i kada ne menjate vrednost primarnog ključa (slika 2.3). Ova poruka se pojavljuje zato što Access "bez razmišljanja" u sva polja kopira vaše vrednosti preko onih koje je drugi korisnik upisao, a da prethodno ne proverava da li je sadržaj određenog polja zaista bio izmenjen. Pošto je jedno od polja (ili više njih) primarni ključ, Jet smatra da je izmenjena i njegova vrednost, pa šalje poruku o nepostojećoj grešci.
Događaj Error na obrascu možete da iskoristite za presretanje grešaka koje su nastale usled sukoba pri upisivanju tako što ćete ih obraditi pomoću VBA koda u proceduri za obradu događaja Error. Vašoj proceduri Access prosleđuje parametre DataErr i Response. Dve najčešće vrednosti parametra DataErr pri optimističkom zaključavanju opisane su u tabeli 2.2.
Greška broj 7787 uzrok je pojavljivanja standardnog okvira za dijalog Write Conflict. Ona nastaje kada korisnik pokuša da snimi izmene zapisa čiji je sadržaj već izmenjen. Greška broj 7878 nastaje kada korisnik počne da menja zapis čiji je sadržaj drugi korisnik izmenio nakon što mu je prvi korisnik pristupio; nastajanje ove greške je verovatnije u slučaju dugih intervala osvežavanja.
Kao vrednost parametra Response možete zadati jednu od sledećih ugrađenih konstanti:
Zapazićete da ne postoji način da naložite Jetu da zapis popuni "vašim" izmenama. Osim zadavanja ugrađene poruke o grešci, vaša jedina preostala mogućnost jeste da dopustite Jetu da, umesto izmena koje je uneo tekući korisnik, upiše izmene koje je drugi korisnik već uneo. Postupak koji se pokreće izborom opcije Save Record deo je Accessovog korisničkog interfejsa koji ne može da se menja u kodu. Međutim, mi smo osmislili zamenu koja daje isti rezultat, ali bez greške koju smo pomenuli u prethodnom odeljku.
Procedura za obradu događaja Error u obrascu frmCustomerOptimistic2 u bazi podataka CH02APP.MDB islustruje tu zamenu. Listing 2.5 sadrži proceduru za obradu događaja i prateći kôd u proceduri za obradu događaja Current u obrascu frmCustomerOptimistic2.
napomena |
U ovom obrascu koristi se DAO kôd za rad sa "pozadinskim" skupom podataka, ali se lako može izmeniti tako da se umesto njega koristi ADO skup podataka. |
|
Const adhcErrWriteConflict = 7787
Const adhcErrDataChanged = 7878
Private Sub Form_Error(DataErr As Integer, _
' Obrađuje greške koje se pojavljuju u obrascu
' Grananje u zavisnosti od broja greške
' Greška usled sukoba pri upisivanju podataka
strMsg = "Drugi korisnik je izmenio sadržaj ovog zapisa " & _
"nakon što ste počeli da ga ažurirate." & vbCrLf & vbCrLf & _
"Æelite li da preuzmete izmene koje je drugi korisnik uneođ" & _
"Izaberite Yes da biste ih preuzeli, " & vbCrLf & _
"ili No da biste upisali svoje izmene."
vbYesNo + vbDefaultButton1 + vbQuestion, _
"Sukob pri upisivanju podataka")
' Pošto Jet prihvata samo upisivanje izmena
' koje je drugi korisnik uneo, moramo prevariti
' Jet tako da prihvati naše izmene. To postižemo
' tako što naše izmene upisujemo u dopunski zapis
' čiji će sadržaj Access zatim upisati preko naših
' Formiramo skup zapisa koji sadrži samo jedan
' zapis u kome se nalazi kopija vrednosti primarnog
' ključa u trenutku kada smo započeli ažuriranje
' zapisa. Ta vrednost je bila zabeležena u proceduri za
' obradu događaja Current. To je neophodno zato što
' može da se promeni i vrednost primarnog ključa.
Set rst = db.OpenRecordset("SELECT * FROM " & _
"tblCustomer WHERE [CustomerId] = " _
' Proveravamo da li je drugi korisnik
' promenio vrednost primarnog ključa.
strMsg = "Drugi korisnik je izmenio " & _
"sadržaj polja CustomerID u ovom zapisu. " & _
"Trebalo bi da osvežite sadržaj zapisa " & _
MsgBox strMsg, vbOKOnly + vbInformation, _
' Ažuriramo sadržaj dopunskog zapisa
' izmenjenim vrednostima sa obrasca.
' Ovim nalažemo osvežavanje sadržaja zapisa
' Ova greška nastaje kada Access otkrije da je
' drugi korisnik izmenio zapis pošto smo mu mi
' pristupili da bismo uneli izmene u njega. Nema razloga
' za brigu, jer još nismo uneli nikakvu izmenu.
strMsg = "Drugi korisnik je izmenio sadržaj ovoga " & _
"zapisa od trenutka kada ste počeli da radite s njim." _
"Pre nego što nastavite, zapis će biti osvežen izmenama " & _
"koje je drugi korisnik uneo."
MsgBox strMsg, vbOKOnly + vbInformation, _
' Ovim nalažemo osvežavanje zapisa
' U ostalim slučajevima Access treba
' da prikazuje standardnu poruku o grešci.
Debug.Print DataErr, Error(DataErr)
' Može se dogoditi da naiđemo na sopstvenu grešku dok
' obrađujemo grešku u vezi s podacima. Na primer,
' neko bi mogao da pesimistički zaključa zapis dok
' mi pokušavamo da ga ažuriramo.
' U tom slučaju korisniku javljamo da postoji greška
MsgBox "Error " & Err.Number & ": " & Err.Description, _
vbOKOnly + vbCritical, "Error Handler Error"
Kada nastane greška broj 7787, obrazac frmCustomerOptimistic2 prikazuje poruku o sukobu pri upisivanju podataka (slika 2.4). Ako korisnik izabere opciju Yes, parametru Response procedura dodeljuje vrednost acDataErrContinue i prekida rad. Ako korisnik izabere opciju No, procedura preuzima vrednosti iz zapisa čiji je sadržaj trenutno prikazan na obrascu (tekući zapis) i kopira ih u nov skup podataka koji sadrži kopiju zapisa na kome je nastao sukob. Zatim, kada procedura za obradu događaja postavi vrednost parametra Response na acDataErrContinue i završi sa radom, Jet kopira "naše" vrednosti preko onih koje sadrži tekući zapis, a to je jednako ponašanje kao kada u okviru za dijalog Write Conflict izaberemo opciju Save Record. Međutim, pošto kopiramo samo sadržaje polja koji se razlikuju od vrednosti koje je drugi korisnik upisao, izbegavamo poruku o narušavanju referencijalnog integriteta:
Suština ove zamene je u tome što se u proceduri za obradu događaja Current vrednost primarnog ključa upisuje u promenljivu mvarCustomerId koja je globalna na nivou modula. To moramo da uradimo zato što treba da otvorimo nov skup podataka koji će sadržati podatke iz tekućeg zapisa, ali ništa nam ne garantuje da korisnik nije izmenio vrednost primarnog ključa. To je razlog što koristimo vrednost primarnog ključa koju smo zabeležili u događaju Current. Druga mogućnost je da dozvolimo samo čitanje sadržaja polja primarnog ključa, ili da koristimo polje tipa AutoNumber koje samo po sebi ne dozvoljava upisivanje.
Ako u svom obrascu koristite kombinovan primarni ključ ili upit koji obuhvata više tabela, moraćete da zabeležite vrednosti više polja.
Obrasci u kojima se primenjuje pesimističko zaključavanje eliminišu sukobe pri upisivanju podataka jer je, u svakom trenutku, samo jednom korisniku dozvoljeno da menja sadržaj zapisa. Pojava ikone sa precrtanim slovom O obaveštava druge korisnike da je zapis zaključan, kao što je to pokazano na primeru obrasca frmCustomerPessimistic1 iz baze podataka koja je pridružena ovom poglavlju, CH02APP (slika 2.6).
Pošto Access 2000 podržava pravo zaključavanje na nivou zapisa, pesimističko zaključavanje je znatno prihvatljivija opcija. Pa ipak, čak i kada korisnici zaključavaju pojedinačne zapise, ne postoji ništa što bi ih sprečilo da određeni zapis blokiraju preterano dugo. Svi smo čuli priču o korisniku koji je počeo da ažurira zapis, a zatim je otišao na ručak, ili, još gore, na dvonedeljni odmor!
Napisali smo modul klase, LockTimeout, koji možete da iskoristite da biste obrascu dodali mogućnost vremenskog ograničavanja. U obrascu frmCustomerPessimistic2, koji je prikazan na slikama 2.7 i 2.8, primenom klase LockTimeout postiže se to da se izmene koje korisnik nije snimio posle deset minuta automatski poništavaju.
Programski kôd pridružen obrascu frmCustomerPessimistic2 prikazan je u listingu 2.6. U proceduri za obradu događaja Load obrasca, pravimo primerak objekta mltoCustomer i pozivamo metodu BindForm da bismo obrazac povezali sa klasom LockTimeout. Metoda BindForm prihvata četiri argumenta:
' Objektna promenljiva tipa LockTimeout
Private mltoCustomer As LockTimeout
Private Sub Form_AfterUpdate()
' Odbrojavanje vremena prekidamo kada korisnik
Private Sub Form_Dirty(Cancel As Integer)
' Započinjemo odbrojavanje vremena od trenutka kada
' korisnik izmeni sadržaj nekog od polja zapisa
' Formiramo objekat tipa LockTimeout
Set mltoCustomer = New LockTimeout
' LockTimeout objekat povezujemo sa tekućim obrascem
FormRef:=Me, LabelRef:=lblLockStatus, _
CheckInterval:=1, TimeoutPeriod:=10 * 60
' Ispitujemo šta treba da se uradi
Modul klase LockTimeout prikazan je u listingu 2.7. U njemu se pomoću Windowsove API funkcije timeGetTime odbrojava vreme. Klasa LockTimeout koristi pet privatnih promenljivih za evidentiranje sledećih podataka: vreme u kome korisnik drži zapis zaključan, referenca na "povezan" obrazac, referenca na objekat tipa natpis koji treba ažurirati, interval objekta tipa Timer i dužina vremenskog ograničenja.
Metoda BindForm povezuje instancu klase sa zadatim obrascem tako što dodeljuje vrednosti svim privatnim promenljivama na nivou modula, ali osim toga ne radi ništa drugo.
Pozivanjem API funkcije timeGetTime, metoda StartTimer beleži tačno vreme početka odbrojavanja i izaziva događaj Timer u obrascu.
Metoda StopTimer prekida događaj Timer obrasca i dodeljuje vrednost 0 promenljivoj mlngLockStart, u kojoj se čuva vreme početka odbrojavanja. Osim toga, ova metoda prazni i tekst natpisa o statusu.
Metoda CheckTimer odrađuje veći deo posla koji klasa obavlja. Pošto se ne generiše nikakav događaj kada se ponište izmene unete u obrazac, ova metoda najpre ispituje svojstvo Dirty obrasca da bi utvrdila da li je bilo izmena. Ako nije, promenljivoj mlngLockStart metoda dodeljuje vrednost 0.
Ako je bilo izmena, CheckTimer izračunava koliko vremena korisnik drži obrazac (zapis) zaključan i koliko još vremena preostaje pre nego što izmene koje je korisnik uneo budu "prisilno" poništene. Metoda zatim izvršava jednu od sledeće tri akcije: ako je vreme isteklo, pozivanjem metoda Undo obrasca poništavaju se izmene koje je korisnik uneo; ako je isteklo više od 90% intervala čekanja, korisnik se na to upozorava; ako je isteklo manje od 90% intervala čekanja, korisnik se o tome samo obaveštava.
' Koristi se za obračunavanje vremena
Private Declare Function timeGetTime Lib "winmm.dll" () _
Private mlngLockStart As Long ' Vreme kada je korisnik zaključao zapis
Private mfrmBound As Form ' Obrazac koji je povezan sa objektom
Private mlngTimerInterval As Long ' Koliko često treba da ispitujemo
Private mlblStatus As Label ' Objekat tipa Label za ispisivanje statusa
Private mlngTimeout As Long ' Koliko dugo čekamo
Public Sub BindForm(FormRef As Form, LabelRef As Label, _
CheckInterval As Long, TimeoutPeriod As Long)
' Ova procedura povezuje instancu klase LockTimeout sa obrascem
mlngTimerInterval = CheckInterval
' Započinjemo odbrojavanje; zapis je zaključan.
mfrmBound.TimerInterval = mlngTimerInterval * 1000
' Prekidamo odbrojavanje, jer zapis više nije zaključan.
' Ova procedura ispituje stanje brojača vremena
' i preduzima odgovarajuće mere koje zavise od
' Osim toga, ona prekida odbrojavanje vremena
' ukoliko je korisnik upisao u bazu izmene koje je uneo,
Dim lngLockDuration As Long ' Koliko dugo je zapis zaključan.
Dim lngTimetoTimeout As Long ' Koliko je vremena preostalo.
' Proveravamo da li je zapis izmenjen.
' Ako je zapis izmenjen, izvršavamo jednu od sledećih akcija:
' 1. Poništavamo izmene (čime otključavamo zapis) ako je
' 2. Upozoravamo korisnika ako je isteklo 90%
' 3. Inače (ako je isteklo manje od 90% vremena), samo
' obaveštavamo korisnika koliko mu je vremena preostalo.
' Pošto izmene još uvek nisu snimljene, ima da se radi.
(timeGetTime() - mlngLockStart) / 1000
If lngLockDuration >= mlngTimeout Then
"Vreme čekanja je isteklo! " & _
"Eventualne izmene ovog zapisa koje" & _
"niste snimili, izgubljene su."
ElseIf lngLockDuration >= 0.9 * mlngTimeout _
"Ovaj zapis ste predugo držali " & _
"Ako izmene koje ste uneli ne snimite na disk" & _
" u roku od " & lngTimetoTimeout & _
' Obaveštenje o proteklom vremenu
mlblStatus.ForeColor = vbBlack
"Ovaj zapis držite zaključan " & _
lngLockDuration & " sekundi. " & _
lngTimetoTimeout & " sekundi."
' Sledeća naredba je neophodna da bi
' obrazac ažurirao tekst natpisa.
Da biste klasu LockTimeout ugradili u jedan od svojih obrazaca, uradite sledeće:
U nekoliko narednih odeljaka razmatraju se pitanja u vezi sa zaključavanjem DAO i ADO skupova podataka.
Vrednost opcije Default Record Locking nema uticaja kada u kodu otvarate DAO skupove podataka pozivanjem metode OpenRecordset. U tom slučaju Access primenjuje pesimističko zaključavanje (važi opcija Edited Record), osim ako: a) parametar Options metode OpenRecordset postavite na dbDenyWrite ili dbDenyRead, ili b) izmenite vrednost svojstva LockEdits. Kada koristite opciju dbDenyWrite, zabranjujete ažuriranje svih slogova. Možete ići i korak dalje, tj. da zabranite i menjanje i učitavanje sadržaja skupova podataka, ali samo onih koje pravite na osnovu tabela, tako što ćete zadati opciju dbDenyRead, kojom uvodite najviši stepen ograničenja pristupa podacima. (Ove opcije mogu da se primenjuju samo na Accessove standardne tabele.)
napomena |
Na skupove podataka tipa Snapshot (slika podataka) ne mogu se primenjivati nikakve vrste zaključavanja, jer su oni već po svojoj prirodi takvi da je moguće samo čitanje njihovog sadržaja. |
|
Pomoću svojstva LockEdits skupa podataka možete da zadate način zaključavanja koji će se primenjivati. Standardna vrednost True čini da se primenjuje pesimističko (Edited Record) zaključavanje. Optimističko zaključavanje (No Locks) možete da zadate ako ovom svojstvu zadate vrednost False.
Na primer, procedura LockingPessDAO u modulu basRecordsetLocking baze podataka CH02APP.MDB (videti listing 2.8) otvara skup podataka na osnovu tabele tblCustomer, pri čemu primenjuje pesimističko zaključavanje.
Dim rstPessimistic As DAO.Recordset
db.OpenRecordset("tblCustomer", dbOpenDynaset)
' Sledeća naredba nalaže Accessu da primenjuje
' pesimističko zaključavanje u skupu podataka
rstPessimistic.LockEdits = True
' Zapis je zaključan između pozivanja
rstPessimistic!City = "Detroit"
Na sličan način, procedura LockingOptDAO u modulu basRecordsetLocking (videti listing 2.9) otvara skup podataka na osnovu tabele tblCustomer, ali se ovog puta primenjuje optimističko zaključavanje.
Dim rstOptimistic As DAO.Recordset
db.OpenRecordset("tblCustomer", dbOpenDynaset)
' Sledeća naredba nalaže Accessu da u skupu podataka
' primenjuje optimističko zaključavanje
rstOptimistic.LockEdits = False
rstOptimistic!City = "Chicago"
' Zapis je zaključan od trenutka izdavanja
' naredbe Update do upisivanja podataka
Kada u višekorisničkom okruženju koristite nevezane skupove podataka, neophodno je da presrećete i da obrađujete sve greške koje u takvom okruženju nastaju. Najčešće među njima pobrojane su u tabeli 2.3. Greška broj 3197 ekvivalentna je grešci sukob pri upisivanju, broj 7787, koja nastaje u vezanim obrascima.
Pošto pri zaključavanju na nivou zapisa nije poznato ime korisnika koji je zaključao zapis, Microsoft je dodao dve nove greške, broj 3202 i broj 3218. Te greške su ekvivalentne greškama broj 3186 i 3260, ali bez imena korisnika i mašine.
Kada radite sa skupovima podataka, neophodno je da predvidite i obradite greške opisane u tabeli 2.3. Da biste obradili slučaj zaključanog zapisa, u svoju aplikaciju verovatno ćete ugraditi blok za obradu grešaka koji će sadržati određen oblik petlje za ponovno pokušavanje kako biste više puta pokušali da pristupite zaključanom zapisu. Modul basRecordsetLocking u bazi CH02APP.MDB sadrži petlju koja ponavlja pokušaje. Ta procedura, LockingPessDAO2, zajedno sa pripadajućim konstantama na nivou modula, prikazana je u listingu 2.10.
' Brojevi grešaka koje se pojavljuju
Const adhcLockErrCantSave1 = 3186
Const adhcLockErrCantSave2 = 3202
Const adhcLockErrCantRead = 3187
Const adhcLockErrExclusive = 3189
Const adhcLockErrDatChngd = 3197
Const adhcLockErrCantUpdate1 = 3188
Const adhcLockErrCantUpdate2 = 3260
Const adhcLockErrCantUpdate3 = 3218
' Donja granica opsega vrednosti za
' interval čekanja između dva pokušaja.
' Gornja granica opsega vrednosti za
' interval čekanja između dva pokušaja.
' Pesimističko zaključavanje sa blokom
Dim rstPessimistic As DAO.Recordset
db.OpenRecordset("tblCustomer", dbOpenDynaset)
' Sledeća naredba nalaže Accessu da primenjuje
' pesimističko zaključavanje u skupu podataka
rstPessimistic.LockEdits = True
' Zapis je zaključan između pozivanja
Debug.Print "Pokušavam da zaključam zapis radi ažuriranja."
rstPessimistic!City = "Detroit"
Debug.Print "Zapis je uspešno ažuriran!"
intRetryCount = intRetryCount + 1
If intRetryCount <= adhcLockRetries Then
Debug.Print "Neuspešno zaključavanje, pokušaj br." & _
' Windowsu i mašini Jet dajemo priliku
' Interval između dva pokušaja određujemo
' na osnovu broja prethodnih pokušaja kome
' dodajemo nasumično odabran broj.
lngWait = intRetryCount ^ 2 * _
Int((adhcLockUBound - adhcLockLBound + 1) _
' Ovde se izvršava prazna petlja, a Windowsu
' omogućavamo da za to vreme obavlja druge
MsgBox("Procedura treba da ažurira " & _
"zapis koji je drugi korisnik zaključao. " & _
&vbCrLf & "Æelite li da ponovo " & _
"pokušate da ažurirate zapisđ" & _
"Pritisnite Yes da biste ponovili pokušaj, " & _
"ili No da biste odustali.", _
"Premašen maksimalan broj pokušaja")
"Procedura će ponovo pokušati " & _
"ažuriranje podataka. " & vbCrLf & _
"Napomena: Neće biti dodatne poruke " & _
"ukoliko ažuriranje bude " & _
vbInformation, "Ponavljanje pokušaja ažuriranja"
Debug.Print "Brojač pokušaja postavljen na 0."
MsgBox "Ažuriranje zapisa nije uspelo!", _
vbOKOnly + vbCritical, "Ažuriranje prekinuto"
Debug.Print "Ažuriranje prekinuto."
' Sadržaj zapisa se promenio dok ga je korisnik ažurirao
' uz optimističko zaključavanje.
MsgBox "Drugi korisnik je izmenio sadržaj zapisa " & _
"Pritisnite OK da biste preuzeli " & _
"Možete potom da unesete svoje izmene i da ih snimite.", _
vbExclamation + vbOKOnly, "Sukob pri upisivanju"
MsgBox "Greška broj " & Err.Number & ": " & _
Err.Description, vbOKOnly + vbCritical, _
U bloku za obradu grešaka najpre ispitujemo da li se radi o jednoj od grešaka koje mogu da nastanu pri pokušaju ažuriranja zaključanog zapisa. Ako se dogodila jedna od njih, sadržaj promenljive intRetryCount povećavamo za jedan. Tu promenljivu koristimo kao brojač pokušaja:
intRetryCount = intRetryCount + 1
Dalje, ispitujemo da li je vrednost brojača intRetryCount manja od maksimalnog broja pokušaja (adhcLockRetries). Ako je tako, pozivamo metodu DAO.DBEngine.Idle da bismo Jetu omogućili da "uhvati korak" i odradi poslove koje nije stigao da obavi, poput oslobađanja ranije zaključanih zapisa koje nije imao vremena da uradi.
If intRetryCount <= adhcLockRetries Then
Debug.Print "Neuspešno zaključavanje, pokušaj br." & _
' Mašini Jet dajemo priliku da uhvati korak.
Možda ćemo pozivanjem metode DBEngine.Idle uspeti da oslobodimo zaključan zapis, ali je vrlo verovatno da ćemo morati da sačekamo nekoliko sekundi da to učini drugi korisnik ili proces. To je razlog što je u blok za obradu greške ugrađen "pametan" kôd za čekanje. Verovatno ćete sličan kôd ugraditi u sve svoje procedure u kojima mogu da nastanu greške pri zaključavanju zapisa. Princip je sledeći: vrednost promenljivoj lngWait, tipa Long Integer, dodeljujemo pomoću formule koja najpre izračunava kvadrat prethodnog broja pokušaja, zatim rezultat množi nasumično odabranom vrednošću koja se nalazi u opsegu između date donje i gornje granice. Zatim petlju For ... Next izvršavamo lngWait puta da bismo sačekali određeno vreme. Naredba DoEvents u petlji omogućava Windowsu da odradi druge poslove (ako ih ima) dok čekamo. Na osnovu nasumično izabranog broja i kvadrata ukupnog broja prethodnih pokušaja petlja pokušava da razdvoji dva korisnika koji istovremeno zahtevaju zaključavanje. Posle petlje For...Next, ponovni pokušaj se ostvaruje pomoću naredbe Resume.
Evo koda petlje za ponovno pokušavanje:
lngWait = intRetryCount ^ 2 * _
Int((adhcLockUBound - adhcLockLBound + 1) _
' Ovde se izvršava prazna petlja, ali Windowsu
' omogućavamo da za to vreme obavlja druge
Ako je premašen maksimalan broj pokušaja, blok za obradu grešaka obaveštava korisnika (u nekim situacijama možda to nećete želeti) i pruža mu mogućnost da pokuša ponovo ili da odustane:
MsgBox("Procedura treba da ažurira " & _
"zapis koji je drugi korisnik zaključao. " & _
&vbCrLf & "Æelite li da ponovo " & _
"pokušate da ažurirate zapisđ" & _
"Pritisnite Yes da biste ponovili pokušaj, " & _
"ili No da biste odustali.", _
"Premašen maksimalan broj pokušaja")
"Procedura će ponovo pokušati " & _
"ažuriranje podataka." & vbCrLf & _
"Napomena: Neće biti dodatne poruke " & _
"ukoliko ažuriranje bude " & _
vbInformation, "Ponavljanje pokušaja ažuriranja"
Debug.Print "Brojač pokušaja postavljen na 0."
MsgBox "Ažuriranje zapisa nije uspelo!", _
vbOKOnly + vbCritical, "Ažuriranje prekinuto"
Debug.Print "Ažuriranje prekinuto."
Blok za obradu grešaka sadrži i naredbu Case koja obrađuje sukobe pri upisivanju. Kôd ne radi ništa posebno, ali je kao primer dovoljan da shvatite osnovnu ideju:
' sadržaj zapisa se promenio dok ga je korisnik ažurirao
' uz optimističko zaključavanje.
MsgBox "Drugi korisnik je izmenio sadržaj " & _
"zapisa s kojim ste radili." & _
" Pritisnite OK da biste preuzeli " & _
"Možete potom da unesete svoje izmene i da ih snimite.", _
Ako drugačije ne zadate, ADO skup podataka se uvek otvara samo za čitanje. Međutim, ako svojstvu LockType objekta Recordset zadate odgovarajuću vrednost (pre pozivanja metode Open), ili ako koristite argument LockType metode Recordset.Open, možete da odredite vrstu zaključavanja koja će se koristiti.
Vrstu i trenutak zaključavanja možete da zadate pomoću jedne od sledećih konstanti:
Kôd prikazan u listingu 2.11 (procedura LockingPessADO u modulu basRecordsetLocking) pokazuje kako se otvara skup podataka sa pesimističkim zaključavanjem. Obratite pažnju na to da u ovom primeru nismo koristili svojstvo CurrentProject. Connection, jer ta posebna vrsta veze s bazom podataka ne omogućava pesimističko zaključavanje.
Dem rstPessimistic As ADODB.Recordset
' Da biste u Accessu mogli da primenjujete
' pesimističko zaključavanje, treba da otvorite novu
' CurrentProject.Connection ne podržava
' Imajte u vidu da svojstvo CurrentProject.BaseConnectionString
' sadrži parametre tekuće veze s bazom podataka.
Set cnn = New ADODB.Connection
cnn.Open CurrentProject.BaseConnectionString
Set rstPessimistic = New ADODB.Recordset
' Otvaramo skup podataka tipa Keyset
' sa pesimističkim zaključavanjem
rstPessimistic.Open "tblCustomer", cnn, adOpenKeyset, _
rstPessimistic!City = "Chicago"
' Zapis se zaključava u trenutku upisivanja
Listing 2.12 sadrži ekvivalentan primer, LockingOptADO, ali sa optimističkim zaključavanjem.
Dim rstOptimistic As ADODB.Recordset
Set cnn = CurrentProject.Connection
Set rstOptimistic = New ADODB.Recordset
' Otvaramo skup podataka tipa Keyset
' sa optimističkim zaključavanjem
rstOptimistic.Open "tblCustomer", cnn, adOpenKeyset, _
rstOptimistic!City = "Chicago"
' Zapis se zaključava u trenutku upisivanja
Slično kao u DAO modelu, kada radite sa ADO skupovima podataka, treba da predvidite i da obradite sve greške opisane u tabeli 2.3. To najčešće znači da treba da ugradite blok za obradu grešaka koji sadrži petlju za ponovno pokušavanje, isto kao što smo to uradili u DAO primeru. U bazi podataka CH02APP.MDB nalazi se primer procedure s takvom petljom za ponovno pokušavanje. To je procedura LockingPessADO2 koja je prikazana u listingu 2.13.
' Brojevi grešaka koje se pojavljuju
Const adhcLockErrCantSave1 = 3186
Const adhcLockErrCantSave2 = 3202
Const adhcLockErrCantRead = 3187
Const adhcLockErrExclusive = 3189
Const adhcLockErrDatChngd = 3197
Const adhcLockErrCantUpdate1 = 3188
Const adhcLockErrCantUpdate2 = 3260
Const adhcLockErrCantUpdate3 = 3218
' Donja granica opsega vrednosti za
' interval čekanja između dva pokušaja.
' Gornja granica opsega vrednosti za
' interval čekanja između dva pokušaja.
' Pesimističko zaključavanje sa blokom
Dim rstPessimistic As ADODB.Recordset
' Da biste u Accessu mogli da primenjujete
' pesimističko zaključavanje, treba da otvorite novu
' CurrentProject.Connection ne podržava
' Imajte u vidu da svojstvo CurrentProject.BaseConnectionString
' sadrži parametre tekuće veze s bazom podataka.
Set cnn = New ADODB.Connection
cnn.Open CurrentProject.BaseConnectionString
Set rstPessimistic = New ADODB.Recordset
' Otvaramo skup podataka tipa Keyset s pesimističkim zaključavanjem
rstPessimistic.Open "tblCustomer", cnn, adOpenKeyset, _
rstPessimistic!City = "Chicago"
' Zapis se zaključava u trenutku upisivanja
Debug.Print "Zapis je uspešno ažuriran!"
' Moramo da koristimo svojstvo SQLState
' da bismo dobili izvorni broj Jet greške.
' Pretpostavka: generiše se samo jedan
Select Case cnn.Errors(0).SQLState
intRetryCount = intRetryCount + 1
If intRetryCount <= adhcLockRetries Then
Debug.Print "Greška pri zaključavanju, pokušaj br. " & _
' Windowsu i Jetu dajemo priliku da uhvate korak
' Interval između dva pokušaja određujemo
' na osnovu broja prethodnih pokušaja kome
' dodajemo nasumično odabran broj.
lngWait = intRetryCount ^ 2 * _
Int((adhcLockUBound - adhcLockLBound + 1) _
' Ovde se izvršava prazna petlja, a Windowsu
' omogućavamo da za to vreme obavlja druge
MsgBox("Procedura treba da ažurira " & _
"zapis koji je drugi korisnik zaključao. " & _
&vbCrLf & "Æelite li da ponovo " & _
"pokušate da ažurirate zapisđ" & _
"Pritisnite Yes da biste ponovili pokušaj, " & _
"ili No da biste odustali.", _
"Premašen maksimalan broj pokušaja")
"Procedura će ponovo pokušati " & _
"ažuriranje podataka. " & vbCrLf & _
"Napomena: Neće biti dodatne poruke " & _
"ukoliko ažuriranje bude " & _
vbInformation, "Ponavljanje pokušaja ažuriranja"
Debug.Print "Brojač pokušaja postavljen na 0."
MsgBox "Ažuriranje zapisa nije uspelo!", _
vbOKOnly + vbCritical, "Ažuriranje prekinuto"
Debug.Print "Ažuriranje prekinuto."
' Sadržaj zapisa se promenio dok ga je korisnik ažurirao
' uz pesimističko zaključavanje.
MsgBox "Drugi korisnik je izmenio sadržaj zapisa " & _
"Pritisnite OK da biste preuzeli " & _
"Možete potom da unesete svoje izmene i da ih snimite.", _
vbExclamation + vbOKOnly, "Sukob pri upisivanju"
MsgBox "Greška broj " & cnn.Errors(0).SQLState & _
": " & cnn.Errors(0).Description, _
vbOKOnly + vbCritical, "Nepredviđena greška"
Blok za obradu grešaka u listingu 2.13 veoma je sličan DAO bloku za obradu grešaka u listingu 2.10. Više detalja o tome kako on radi i kako da ga testirate naći ćete u objašnjenju listinga 2.10.
Transakciona obrada (engl. transaction processing) je izraz iz oblasti baza podataka koji znači grupisanje izmena sadržaja baze podataka u "paket" koji se potom obrađuje kao neraskidiva celina. Obrada se uvek odvija tako da se uspešno izvrše ili sve transakcije, ili nijedna od njih. Na primer, kada u nekoj aplikaciji za bankarsko poslovanje premeštate iznos sa jednog računa na drugi, ne biste želeli da stanje na jednom računu povećate, a da ga pri tom na drugom računu ne smanjite. Zato ćete te dve izmene grupisati u jednu transakciju.
Transakciona obrada je korisna u aplikacijama u kojima jedna akcija mora da se izvrši u vezi sa jednom ili više drugih akcija. Transakciona obrada je uobičajena u bankarskim, računovodstvenim i mnogim drugim aplikacijama.
DAO model podržava transakcionu obradu pomoću sledećih metoda objekta Workspace:
Pozivanjem metode BeginTrans označava se početak niza operacija koje čine jednu logičku jedinicu. Metoda CommitTrans preuzima sve izmene načinjene od poslednjeg mesta na kome je bila pozvana metoda BeginTrans i upisuje ih na disk. Metoda Rollback deluje na suprotan način od CommitTrans: ona poništava sve izmene i vraća stanje kakvo je bilo pre poslednjeg poziva metode CommitTrans. Najjednostavniji oblik DAO transakcione obrade najčešće je sličan sledećem:
Dim wrkCurrent As DAO.WorkSpace
Set wrkCurrent = DAO.DBEngine.Workspaces(0)
' (Izmene podataka počinju odavde)
Kada koristite DAO transakcionu obradu, trebalo bi da vodite računa o sledećem:
savet |
Obrasci Accessa 2000 omogućavaju povezivanje skupa podataka bilo kog tipa sa obrascem. Ova novina omogućava korišćenje DAO transakcija u vezanim obrascima. |
|
ADO model podržava transakcionu obradu pomoću sledećih metoda objekta Connection:
Pozivanjem metode BeginTrans označava se početak niza operacija koje čine jednu logičku jedinicu. Metoda CommitTrans preuzima sve izmene načinjene od poslednjeg mesta na kome je bila pozvana metoda BeginTrans i upisuje ih na disk. Metoda RollbackTrans deluje na suprotan način od CommitTrans: ona poništava sve izmene i vraća stanje kakvo je bilo pre poslednjeg poziva metode CommitTrans. U svom najjednostavnijem obliku, ADO transakciona obrada veoma je slična DAO transakcionoj obradi:
Set cnn = CurrentProject.Connection
' (Izmene podataka počinju odavde)
' (Preostali deo bloka za obradu grešaka)
Kao što vidite, jedina razlika je to što u ADO modelu metode za transakcionu obradu pripadaju objektu Connection umesto objektu Workspace. Osim toga, DAO metoda Rollback u ADO modelu zove se RollbackTrans.
Transakciona obrada je izuzetno važna u višekorisničkim aplikacijama. Kada više korisnika istovremeno unosi izmene u bazu podataka, više se ne možete pouzdati u to da će uvek jedna izmena biti trajno upisana u bazu pre nego što započnete narednu, osim ako određenu grupu izmena ne "uokvirite" u transakciju. Zbog toga bi u višekorisničkom okruženju trebalo da koristite transakcionu obradu.
Kada koristite transakcije u višekorisničkom okruženju, Jet tretira svaku transakciju kao jednu operaciju pisanja na disk, pri čemu primenjuje način zaključavanja koji ste zadali. To znači da, kada zadate pesimističko zaključavanje u DAO skupu podataka, Jet zaključava zapis kada naiđe na metodu Edit. Kada se koristi ADO, Jet zaključava zapis kada izmenite vrednost nekog od polja skupa zapisa. Kada umesto pesimističkog, koristite optimističko zaključavanje, Jet zaključava zapis kada naiđe na metodu Update; to važi za oba modela, DAO i ADO. Međutim, u okviru jedne transakcije, Jet kumulira zaključavanja zapisa, koje ne oslobađa dok transakcija ne bude završena ili poništena.
Ako Jet naiđe na zaključan zapis unutar transakcije, nastaje standardni skup presretljivih grešaka. Kada se to dogodi, treba da se postarate da zapis uspešno zaključate, ili da poništite transakciju.
Osim izričitih (eksplicitnih) transakcija koje formirate metodama BeginTrans, CommitTrans i Rollback, Jet 4.0 (i njegovi prethodnici od Jeta 3.0) formira i implicitne transakcije da bi poboljšao performanse operacija nad skupovima podataka. U bazi podataka koju je korisnik otvorio za isključivu upotrebu, Jet standardno završava implicitne transakcije svake 2 sekunde; kada je baza podataka otvorena za deljenu upotrebu, Jet završava te transakcije svakih 50 milisekundi. U višekorisničkom okruženju ne bi trebalo da standardna vrednost od 50 milisekundi bude uzrok primetnog smanjenja konkurentnosti.
Tačan učinak koji implicitne transakcije imaju na konkurentnost zavisi od vrednosti više parametara u registru. Ti parametri opisani su u tabeli 12.4. Da biste zadali drugačije vrednosti, treba da ih upišete u parametre registra koji se nalaze u sledećoj grani:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Jet\4.0\Engines\Jet 4.
U nekim aplikacijama ćete namerno žrtvovati konkurentnost u korist boljih performansi. U tim slučajevima može biti korisno da povećate vrednost ključa SharedAsyncDelay.
savet |
Jetove parametre u registru možete da nađačate ako koristite metodu SetOption DAO objekta DBEngine, ili svojstva ADO objekta Connection. |
|
Accessov korisnički interfejs, kao i DAO i ADO modeli omogućavaju upravljanje ponašanjem transakcija pri izvršavanju akcionih upita.
Na listu svojstava akcionog upita, koji ste snimili u bazu podataka, možete da postavite vrednost svojstva UseTransaction na No da biste sprečili Access da sve akcione upite izvršava unutar jedne transakcije. Kada svojstvo UseTransaction ima vrednost No, mogu se poboljšati performanse akcionih upita koji deluju na veliki broj zapisa, ali zato u slučaju greške nije moguće poništavanje transakcije.
Ponašanje pri transakcionoj obradi možete da izmenite i za DAO uskladištene upite (QueryDef), ali pošto svojstvo UseTransaction nije ugrađeno Jetovo svojstvo, morate ga najpre dodati zbirci svojstava objekta QueryDef pomoću koda sličnog ovom:
' Upit ne treba umetati u transakciju.
Set prp = qdf.CreateProperty("UseTransaction", _
Slično tome, u ADO modelu zadajete da se ne koristi transakcija pri izvršavanju akcionog upita tako što svojstvu Jet OLEDB:Bulk Transactions određenog ADO objekta tipa Command dodelite vrednost 1 (koja znači da se transakcije ne koriste; vrednost 2 nalaže Jetu da koristi transakciju):
' Upit ne treba umetati u transakciju.
cmd.Properties("JET OLEDB:Bulk Transactions") = 1
Upiti za ažuriranje (Update) i brisanje (Delete) podataka imaju svojstvo FailOnError koje je povezano sa svojstvom UseTransaction. Standardno ponašanje Accessa je takvo da, kada iz njegovog korisničkog interfejsa pokrenete akcioni upit, dozvoljeno je njegovo delimično izvršavanje posle upozorenja korisniku da ažuriranje određenih zapisa neće uspeti. Međutim, ako vrednost svojstva FailOnError podesite na Yes, Access neće dozvoliti da se upit za ažuriranje izvrši samo delimično.
Kada koristite DAO model, možete da izmenite svojstvo FailOnError skupa podataka, ili da argumentu Options metode QueryDef.Execute dodelite vrednost dbFailOnError, kao u sledećem primeru:
' Upit se ne izvršava ako postoje zapisi koji se
Listing 2.14 prikazuje primer korišćenja opcije dbFailOnError s metodom Execute objekta QueryDef.
Function DeleteTempOrdersDAO()
' Primer korišćenja opcije dbFailOnError
' i svojstva RecordsAffected akcionih upita.
Set wrk = dao.DBEngine.Workspaces(0)
strWhere = "[OrderId] BETWEEN 1 AND 100"
' Utvrđujemo ukupan broj zapisa koje treba izbrisati.
DCount("*", "tblTempOrders", strWhere)
Set qdf = db.CreateQueryDef("", _
"DELETE * FROM tblTempOrders WHERE " & strWhere)
intResp = MsgBox("Pronađeno je " & _
lngRecsEstimated & " zapisa za brisanje." & vbCrLf & _
"Odustati ako brisanje nekog od njih ne bude mogućeđ", _
vbYesNo + vbInformation + vbDefaultButton1, _
' Ovde utvrđujemo ukupan broj zapisa koji
lngRecsAffected = qdf.RecordsAffected
If lngRecsAffected < lngRecsEstimated Then
intResp = MsgBox("Samo " & lngRecsAffected & _
" zapisa može da se briše." & vbCrLf & _
' Poništavamo transakciju ako to korisnik zahteva.
MsgBox "Transakcija poništena!", _
MsgBox "Došlo je do greške. Brisanje poništeno.", _
vbOKOnly + vbCritical, "Primer akcionog upita"
MsgBox "Greška broj " & Err.Number & ": " & _
vbOKOnly + vbCritical, "Primer akcionog upita"
Kada koristite ADO model, možete postići ekvivalentan efekat tako što ćete svojstvu Jet OLEDB:Partial Bulk Ops ADO objekta Command dodeliti vrednost 2 (koja znači da nije dozvoljeno delimično ažuriranje; vrednost 1 znači da je to dozvoljeno):
' Upit se ne izvršava ako postoje zapisi koji se
cmd.Properties("JET OLEDB:Partial Bulk Ops") = 2
Listing 2.15 prikazuje primer korišćenja svojstva Jet OLEDB:Global Partial Bulk Ops ADO objekta Command.
Function DeleteTempOrdersADO()
' Primer korišćenja svojstva "Jet OLEDB:Partial Bulk Ops"
Set cnn = CurrentProject.Connection
strWhere = "[OrderId] BETWEEN 1 AND 100"
' Utvrđujemo broj zapisa koje treba izbrisati.
DCount("*", "tblTempOrders", strWhere)
"DELETE * FROM tblTempOrders WHERE " & strWhere
' Odustajemo u slučaju greškeđ
intResp = MsgBox("Pronađeno je " & _
lngRecsEstimated & " zapisa za brisanje." & vbCrLf & _
" Odustati ako brisanje nekog od njih ne bude mogućeđ", _
vbYesNo + vbInformation + vbDefaultButton1, _
cmd.Properties("JET OLEDB:Partial Bulk Ops") = 2
cmd.Properties("JET OLEDB:Partial Bulk Ops") = 1
If lngRecsAffected < lngRecsEstimated Then
intResp = MsgBox("Samo " & lngRecsAffected & _
" zapisa može da se briše." & vbCrLf & _
MsgBox "Transakcija poništena!", _
MsgBox "Došlo je do greške. Brisanje poništeno.", _
vbOKOnly + vbCritical, "Primer akcionog upita"
MsgBox "Greška broj" & cnn.Errors(0).SQLState & ": " & _
Mašina baze podataka Jet pruža visok stepen prilagodljivosti pri izboru polja tipa AutoNumber (ovaj tip polja zvao se Counter u verzijama pre Accessa 95). Polje tipa AutoNumber možete da podesite tako da sadrži vrednosti tipa Long Integer koje rastu sekvencijalno, ili koje se generišu nasumično, a možete da koristite i 16-bitni jedinstveni identifikator (GUID). (Više informacija o GUID identifikatorima naći ćete u poglavlju 9.) Međutim, ponekad ćete radije koristiti namensku proceduru za generisanje vrednosti koje rastu po određenom algoritmu. Razlozi za to mogu biti sledeći:
Svoj algoritam za generisanje vrednosti u poljima tipa AutoNumber u Accessovoj bazi podataka možete da implementirate pomoću zasebne tabele u kojoj se čuva sledeća AutoNumber vrednost. Tu tabelu morate da zaključavate kada učitavate novu AutoNumber vrednost, kao i da presrećete greške koje mogu da nastanu kada više korisnika istovremeno pokušava da učita novu vrednost iz tabele.
Napisali smo namensku proceduru za generisanje AutoNumber vrednosti za polje MenuID tabele tblMenu u bazi podataka CH02APP.MDB. Procedura, u kojoj se koristi DAO model, poziva se iz obrasca frmMenu; tabela u kojoj se čuva AutoNumber vrednost nalazi se u bazi podataka CH02AUTO.MDB.
napomena |
|
|
Postupak generisanja nove vrednosti za polje tipa AutoNumber podelili smo na dva dela. Procedura nižeg nivoa obrađuje AutoNumber korak povećanja, ili daje -1 ukoliko nova AutoNumber vrednost ne može da se generiše. Druga procedura višeg nivoa dodeljuje AutoNumber vrednost novom zapisu i određuje šta će biti urađeno u slučaju greške. Obe procedure nalaze se u modulu basAutoNumber u bazi podataka CH02APP.MDB.
Funkcija adhGetNextAutoNumber je procedura nižeg nivoa koja direktno komunicira s bazom podataka u kojoj se nalazi tabela koja sadrži sledeću AutoNumber vrednost. Ime te tabele je parametar procedure. Sledeća AutoNumber vrednost čuva se u tabeli (u bazi podataka na koju upućuje konstanta adhcAutoNumDb) čije se ime sastoji od imena ciljne tabele i sufiksa _ID. Na primer, sledeća AutoNumber vrednost za tabelu tblMenu čuva se u tabeli tblMenu_ID. Ova tabela ima vrlo jednostavnu strukturu: sastoji se od samo jednog polja tipa Long Integer koje se zove NextAutoNumber. Funkcija adhGetNextAutoNumber prikazana je u listingu 2.16.
' Baza podataka u kojoj se čuva naredna AutoNumber vrednost.
Const adhcAutoNumDb = "Ch02Auto.Mdb"
' Broj ponavljanja pokušaja zaključavanja
'Donja granica opsega vrednosti za
' interval čekanja između dva pokušaja.
' Gornja granica opsega vrednosti za
' interval čekanja između dva pokušaja.
Const adhcLockErrCantUpdate2 = 3260
Const adhcLockErrTableInUse = 3262
Function adhGetNextAutoNumber(ByVal strTableName _
' Daje sledeću generisanu AutoNumber vrednost
' za određenu tabelu. Te vrednosti se čuvaju u bazi
' podataka na koju upućuje konstanta
' adhcAutoNumDb, u tabelama čija se imena sastoje
' od imena tabela za koje generišu AutoNumber vrednosti
' Povratna vrednost funkcije je -1 ukoliko zbog problema
' zaključavanja nije moguće generisanje AutoNumber
On Error GoTo adhGetNextAutoNumber_Err
Dim rstAutoNum As DAO.Recordset
' Otvaramo skup podataka na osnovu odgovarajuće tabele u
' bazi podataka sa AutoNumber vrednostima. Dok je ona otvorena
' zabranjujemo čitanje svim ostalim korisnicima.
Set wrk = DAO.DBEngine.Workspaces(0)
Set db = wrk.OpenDatabase(adhCurrentDBPath() & _
Set rstAutoNum = db.OpenRecordset(strTableName _
& "_ID", dbOpenTable, dbDenyRead)
' Povećavamo postojeću AutoNumber vrednost za jedan.
' To je i povratna vrednost funkcije.
lngNextAutoNum = rstAutoNum![NextAutoNumber]
rstAutoNum![NextAutoNumber] = lngNextAutoNum + 1
adhGetNextAutoNumber = lngNextAutoNum
' Tabelu je zaključao drugi korisnik
intRetryCount = intRetryCount + 1
' Broj pokušaja premašio maksimum, odustajemo.
If intRetryCount > adhcLockRetries Then
Resume adhGetNextAutoNumber_Exit
' Omogućavamo Windowsu i Jetu da uhvate korak.
' Interval između dva pokušaja određujemo
' na osnovu broja prethodnih pokušaja kome
' dodajemo nasumično odabran broj.
Int((adhcLockUBound - adhcLockLBound + 1) _
' Ovde se izvršava prazna petlja, a Windowsu
' omogućavamo da za to vreme obavlja druge poslove.
MsgBox "Greška broj " & Err.Number & ": " _
vbOKOnly + vbCritical, "adhGetNextAutoNumber"
Resume adhGetNextAutoNumber_Exit
Ukoliko nema sukoba oko AutoNumber vrednosti, posao funkcije adhGetNextAutoNumber svodi se na učitavanje i inkrementiranje polja NextAutoNumber. Međutim, u višekorisničkom okruženju može se dogoditi da jedan korisnik zahteva AutoNumber vrednost iz tabele koju ju je drugi korisnik već zaključao. Funkcija adhGetNextAutoNumber obrađuje tu vrstu grešaka pomoću petlje za ponovno pokušavanje slične onoj koja je opisana u odeljku "DAO petlja za ponovno pokušavanje" u prethodnom delu ovog poglavlja.
Funkcija adhAssignID, koja se takođe nalazi u modulu basAutoNumbers (prikazana je u listingu 2.17), dodeljuje AutoNumber vrednosti na višem nivou. Ona se poziva u proceduri za obradu događaja BeforeInsert u obrascu, pri čemu joj se prosleđuju tri parametra: referenca na objekat tipa Form, ime tabele u kojoj se čuva prilagođena AutoNumber vrednost i ime polja u koje se upisuje ta vrednost. Funkcija adhAssignID odbacuje svaki zapis kome se ne može dodeliti AutoNumber vrednost. U zavisnosti od problema koje rešavate, može vam biti neophodna složenija procedura za kontrolu na višem nivou. Na primer, umesto da odbacujete zapise kojima ne možete da dodelite vrednost, možete da ih smeštate u privremenu lokalnu tabelu.
Function adhAssignID(frm As Form, ByVal _
ByVal strAutoNumField As String) As Variant
' Poziva se iz događaja BeforeInsert obrasca radi
' dodeljivanja nove AutoNumber vrednosti polju.
' Odbacuje svaki zapis koji ne može da bude upisan.
lngNewID = adhGetNextAutoNumber(strTableName)
frm(strAutoNumField) = lngNewID
MsgBox "Dodavanje zapisa nije moguće, jer se ne može " & _
"dodeliti AutoNumber vrednost", vbOKOnly + vbCritical, _
"Greška pri upisivanju zapisa"
MsgBox "Greška broj " & Err.Number & ": " & Err.Description, _
vbOKOnly + vbCritical, "adhAssignID"
Funkciju adhGetNextAutoNumber možete da izmenite tako da generiše vrednosti za AutoNumber polja u nekom posebnom formatu sa korakom povećanja koji je različit od 1, ili u alfanumeričkom formatu.
Mašina Jet 4.0 pruža dve mogućnosti za višekorisničko okruženje pomoću kojih se efikasnije upravlja korisnicima. To su lista korisnika i upravljanje vezom.
Kada koristite ADO model, možete da napravite skup podataka koji je specifičan za određeni izvor podataka (pomoću metode OpenSchema objekta Connection) i koji pruža i podatke o tome koji su korisnici trenutno aktivni u bazi podataka. Da biste to uradili, metodi OpenSchema treba da prosledite "magični" GUID identifikator. Taj identifikator, čija je vrednost {947bb102-5d43-11d1-bdbf-00c04fb92675}, nema nikakvo značenje, osim što je jedini koji vam omogućava da od Jeta dobijete podatke za listu korisnika. (Pomoću metode OpenSchema skup podataka možete da popunite raznim vrstama korisnih podataka o šemi baze podataka. Ovu metodu koristili smo u prethodnom delu poglavlja, u odeljku "Upravljanje pridruženim tabelama".)
Programski kôd u listingu 2.18, koji ćete naći u obrascu frmViewUsers baze podataka CH02APP.MDB, pokazuje kako se upotrebljava lista korisnika.
' Da biste od šeme baze podataka dobili listu korisnika,
' treba da zadate ovaj magični broj. Zbog čega nije definisana
' konstanta za njegađ Ko bi to znao...
Const adhcUsers = "{947bb102-5d43-11d1-bdbf-00c04fb92675}"
' Formira listu trenutnih korisnika baze podataka
' pomoću metode OpenSchema objekta Connection.
strUser = "Computer;UserName;Connectedđ;Suspectđ"
Set cnn = CurrentProject.Connection
Schema:=adSchemaProviderSpecific, _
' Pošto su neke od dobijenih vrednosti znakovni nizovi
' koji se završavaju znakom Null, uklanjamo te
If InStr(varVal, vbNullChar) > 0 Then
InStr(varVal, vbNullChar) - 1)
strUser = strUser & ";" & varVal
Kada joj prosledite odgovarajući parametar SchemaID, metoda OpenSchema puni skup podataka podacima o korisnicima. Međutim, pošto se neke od vrednosti koje metoda OpenSchema daje završavaju znakom Null, procedura BuildUserList odseca sve što se nalazi iza tog znaka pre nego što formira znakovni niz, kojim zatim inicijalizuje objekat tipa ListBox na obrascu frmViewUsers (tako što popunjava njegovo svojstvo RowSource). Primer liste sa tri korisnika koja tako nastaje prikazan je na slici 2.9.
Jet 4.0 pruža mogućnost zvanu upravljanje vezom (koja je poznata i kao pasivno blokiranje) pomoću koje možete da sprečite prijavljivanje korisnika u bazu podataka. To je korisno kada želite da ograničite broj istovremenih korisnika baze podataka, ili kada želite da obavite neki administrativni posao, kao što je pravljenje rezervnih kopija baze podataka. Imajte u vidu da je to pasivna mogućnost, što znači da ne postoji način da iz baze podataka prisilno isključite korisnike koji su trenutno aktivni.
Vezom upravljate pomoću svojstva Jet OLEDB:Connection Control ADO objekta Connection. Da biste sprečili prijavljivanje novih korisnika u bazu podataka, ovom svojstvu zadajte vrednost 1. Da biste ponovo dozvolili prijavljivanje korisnika, ovom svojstvu zadajte vrednost 2.
Listing 2.19 prikazuje mogućnost pasivnog blokiranja baze podataka na primeru obrasca frmViewUsers.
Const adhcAllowUsers = "Allow New Users"
Const adhcDisallowUsers = "Disallow New Users"
Private Sub cmdShutdown_Click()
If cmdShutdown.Caption = adhcDisallowUsers Then
' Zadajemo pasivnu blokadu i inicijalizujemo
Properties("Jet OLEDB:Connection Control") = 1
cmdShutdown.Caption = adhcAllowUsers
' Uklanjamo pasivnu blokadu i u skladu s tim
Properties("Jet OLEDB:Connection Control") = 2
cmdShutdown.Caption = adhcDisallowUsers
Slika 2.10 prikazuje poruku o grešci koja se pojavljuje kada pokušate da pristupite bazi podataka koja je pasivno blokirana.
U nekoliko narednih odeljaka opisano je više pitanja koja ćete u nekim slučajevima razmatrati kada projektujete aplikacije za višekorisnička okruženja.
Iako se pitanja bezbednosti ne postavljaju samo u višekorisničkom okruženju, neosporno je da im u takvom okruženju treba posvetiti više pažnje. Kako raste broj radnih stanica koje koriste vašu aplikaciju, raste i verovatnoća da ćete morati da sprečavate neovlašćene korisnike da pristupaju podacima ili samoj aplikaciji.
Ako primenjujete zaključavanje na nivou stranice, bezbednosni sistem omogućava Accessu da korisnicima daje tačne podatke o tome ko je određeni zapis zaključao. Nažalost, od sadašnje verzije mehanizma za zaključavanje na nivou zapisa nije moguće dobiti podatak o imenu korisnika koji je zapis zaključao.
napomena |
Više detalja o bezbednosnom sistemu naći ćete u poglavlju 8. |
|
Kada prelazite sa jednokorisničkih na višekorisničke aplikacije, treba da počnete da razmišljate o mogućim uskim grlima pri razmeni podataka. Kad god vaša aplikacija zahteva podatke, oni se putem mreže (koja je poznata i kao žica) šalju radnoj stanici. To znači da je neophodno da svedete na minimum slanje velikih količina podataka žicom, kako zbog korisnika koji je zahtevao podatke, tako i zbog opterećenja mreže.
Jedna od oblasti kojima treba posvetiti posebnu pažnju jeste količina zapisa koju ćete na obrascu prikazati korisnicima. Iako se u Accessu obrazac veoma jednostavno povezuje sa celom tabelom ili upitom, brzo ćete ustanoviti da aplikacija loše radi u mreži kada dopustite korisnicima da se bez ikakvih ograničenja šetaju po celom sadržaju skupa zapisa (čak i u slučaju tabela skromne veličine od, npr. 30000 do 50000 zapisa). Mnogo je bolje da korisniku ponudite po jedan zapis u datom trenutku i da u kodu menjate izvor podataka obrasca, nego da pomoću metode Find prelazite na ciljni zapis.
Neophodno je da svoje aplikacije temeljnije testirate ukoliko one treba da rade u mrežnom okruženju. Pravilo koje u tom slučaju treba da imate na umu glasi: "Ako nešto može da pođe naopako, to će se sigurno dogoditi." To znači da u višekorisničkim aplikacijama treba da posvetite znatno više vremena testiranju i otklanjanju grešaka.
Jedna od prednosti razvijanja aplikacija u Windows okruženju jeste to što na istoj mašini možete da projektujete, testirate i otklanjate greške u višekorisničkim aplikacijama pokretanjem dve instance Accessa i prelaženjem sa jedne na drugu. Iako vam takav način rada omogućava da otkrijete i da otklonite mnoge potencijalne probleme, ipak morate da testirate aplikaciju i na ciljnoj mreži, pod opterećenjem uobičajenog broja korisnika koji predviđate, da biste otkrili sve potencijalne probleme. Drugim rečima, nema zamene za stvarne uslove.
Kada razvijate Jet aplikacije za višekorisničko okruženje, važno je da imate na umu da treba da predvidite moguće probleme. U višekorisničke aplikacije treba da bude ugrađena mogućnost obrade grešaka, ili oporavka od grešaka koje nastaju usled zaključavanja zapisa. Ne postoji skup savršenih opštih rešenja koja mogu da se primene na sve višekorisničke Jet aplikacije. Moraćete da razvijete odgovarajuće rešenje za određenu bazu podataka uzimajući u obzir sledeće: