Open table exclusive

J

Jack Leach

Hi...

I have need of a custom Autonumber, and am not sure the best way to open the
table to get the information.

I have a seperate table for every autonumber field that I need, which
contains one field and one record (last used).

I need to exclusively open this using a sleep and timeout wrapper function,
update the number, and close it.

So there's two things I'm hoping to find out:

1) best method for opening the table
2) the trappable error that will be thrown if another attempt is made to
open it while it's already opened


Thanks!

--
Jack Leach
www.tristatemachine.com

"I haven''t failed, I''ve found ten thousand ways that don''t work."
-Thomas Edison (1847-1931)
 
J

Jack Leach

If I understand correctly, I can use a DAO recordset and the OpenRecordset
method with these constants as the Options argument:

dbDenyRead + dbDenyWrite

I'm assuming that if I do this, I won't need to supply any information for
the Lockedits argument, as no other users will be able to get into the table
anyway?

If someone could confirm that this is the correct way to go about it I'd be
much appreciative... haven't worked with this before.

(linked table in a multiuser settings, Access 2003)

--
Jack Leach
www.tristatemachine.com

"I haven''t failed, I''ve found ten thousand ways that don''t work."
-Thomas Edison (1847-1931)
 
J

Jack Leach

If I understand correctly, I can use a DAO recordset and the OpenRecordset
method with these constants as the Options argument:

dbDenyRead + dbDenyWrite


This does not work... the following test:


Dim rs As DAO.Recordset
Dim rs2 As DAO.Recordset

Set rs = Currentdb.OpenRecordset( _
"tblANumPOs", , dbDenyRead + dbDenyWrite)
Set rs2 = Currentdb.OpenRecordset("tblANumPOs")


..... produces no errors and I can still edit and update from rs2

--
Jack Leach
www.tristatemachine.com

"I haven't failed, I've found ten thousand ways that don't work."
-Thomas Edison (1847-1931)
 
K

Ken Snell MVP

Jack, the only way to "lock" a table effectively is to immediately do an
..Update on that record right after you open the recordset. That will put a
record lock on the record.

What you seek to do is an "art" more so than a "practice / process", I've
found. You could write your code to get the next number, then do a
validation to ensure that that number has not already been "used" by someone
who got in before you did. But, doing an .Update immediately, where you
increment the number after you read the current one, is a good approach.

I have written a class module with a function and table that does pretty
much what you seek. I've not released it to my web page yet, though I plan
to do this in the near future. If you're interested in trying it out (it's
been used successfully by others and me in applications), send me an email
using the link found at my web home page (see signature).

--

Ken Snell
<MS ACCESS MVP>
http://www.accessmvp.com/KDSnell/
 
B

Banana

Just to be safe, though... Try explicitly opening two instances of
Access and in one, open with dbDenyRead + dbDenyWrite then in other try
to open the same recordset.

See what happens. We want to rule out the possibility that you're
running the code in same scope messing with the connection/access.
 
D

Douglas J. Steele

Jack Leach said:
This does not work... the following test:


Dim rs As DAO.Recordset
Dim rs2 As DAO.Recordset

Set rs = Currentdb.OpenRecordset( _
"tblANumPOs", , dbDenyRead + dbDenyWrite)
Set rs2 = Currentdb.OpenRecordset("tblANumPOs")


.... produces no errors and I can still edit and update from rs2

While the second argument (type) in the OpenRecordset method is optional, in
many of the Access methods, skipping an optional arguments means that all
subsequent arguments are ignored.

Try either providing a value for the type (dbOpenTable), or use named
arguments:

Set rs = Currentdb.OpenRecordset( _
"tblANumPOs", dbOpenTable, dbDenyRead + dbDenyWrite)

or

Set rs = Currentdb.OpenRecordset( _
"tblANumPOs", Options := dbDenyRead + dbDenyWrite)

(note that's colon-equal sign between Options and dbDenyRead + dbDenyWrite)
 
B

Banana

Douglas said:
While the second argument (type) in the OpenRecordset method is optional, in
many of the Access methods, skipping an optional arguments means that all
subsequent arguments are ignored.

Try either providing a value for the type (dbOpenTable), or use named
arguments:

Set rs = Currentdb.OpenRecordset( _
"tblANumPOs", dbOpenTable, dbDenyRead + dbDenyWrite)

or

Set rs = Currentdb.OpenRecordset( _
"tblANumPOs", Options := dbDenyRead + dbDenyWrite)

(note that's colon-equal sign between Options and dbDenyRead + dbDenyWrite)

Wow. That's my first time I've heard about this behavior (and I'd like
to think I know a little bit about Access... ;) ). I will have to test
and prove this is truly the case.
 
J

Jack Leach

Well I'll be...
Set rs = Currentdb.OpenRecordset( _
"tblANumPOs", dbOpenTable, dbDenyRead + dbDenyWrite)

Attempting to open rs2 with rs opened as above, with the dbOpenTable, does
indeed throw an error (3219, Invalid Operation)

Set rs = Currentdb.OpenRecordset( _
"tblANumPOs", Options := dbDenyRead + dbDenyWrite)

However, the named arguments do not... rs2 can still access the recordset.

Thanks Douglas.

--
Jack Leach
www.tristatemachine.com

"I haven''t failed, I''ve found ten thousand ways that don''t work."
-Thomas Edison (1847-1931)
 
D

Douglas J. Steele

Just in case it makes a difference, try

Set rs = Currentdb.OpenRecordset( _
"tblANumPOs", Options := (dbDenyRead + dbDenyWrite))
 
D

Douglas J. Steele

Banana said:
Wow. That's my first time I've heard about this behavior (and I'd like to
think I know a little bit about Access... ;) ). I will have to test and
prove this is truly the case.

I know it's definitely an issue with the OpenDatabase method: specifying
True for read-only does nothing unless you provide a value for the options
parameter (or use Read-Only := True)

I'm afraid I don't have a comprehensive list of which methods behave this
way.
 
J

Jack Leach

Just in case it makes a difference, try
Set rs = Currentdb.OpenRecordset( _
"tblANumPOs", Options := (dbDenyRead + dbDenyWrite))


Same result... no errors, tblANumPOs is still accessable with a seperate
recordset.


This is interesting behavior... I've never been one to use named arguments,
but quite often I use an optional while leaving some preceeding optionals
blank... I may change some of my practices after this.

I wonder why this is the case??

--
Jack Leach
www.tristatemachine.com

"I haven''t failed, I''ve found ten thousand ways that don''t work."
-Thomas Edison (1847-1931)
 
D

Douglas J. Steele

Is this a linked table?

The Help file does say that dbDenyRead only applies to Microsoft jet
table-type Recordsets, and the default type when you don't supply one is
supposed to be a dynaset-type Recordset for a linked table or query.

At least you've got a work-around.
 
J

Jack Leach

wait wait wait.... I screwed up... here's my test code...

On Error Resume Next
Dim rs As DAO.Recordset
Dim rs2 As DAO.Recordset
Set rs = [....]
Set rs2 = [...]
If Err.Number <> 0 Then MsgBox Err.Num & Err.Description
[...]



unforntunately when I went to test the actual implementation I found that
the error I was seeing is actually being thrown on the first Set rs line:

Set rs = CurrentDb.OpenRecordset( _
"tblANumPOs", dbOpenTable, dbDenyRead + dbDenyWrite)


so... no luck as of yet (this should teach me to use proper error handling
even in a quick test function).

This is indeed a linked table... I shall further look into it. Sorry for
any confusion my errant followup posts may have caused.





--
Jack Leach
www.tristatemachine.com

"I haven't failed, I've found ten thousand ways that don't work."
-Thomas Edison (1847-1931)
 
J

Jack Leach

Ok, I finally got it... failproof...

With linked tables...

Open the recordset dbOpenDynaset, dbDenyWrite

Until this recordset is closed, any other attempts to open it produce error
3008 (laymans terms: the recordset is opened exclusively)

so wrap this in an incremented sleep loop with a timeout, trap the error, etc.

Here's the code... I've tested this every way I can think of and seems to
work great.




'==========START CODE==================
'Jack Leach 2009/09/23
'
'Gets the next increment of a custom autonumber
'
'Prevents multiple users from connected at once through the following:
' on a linked table, a recordset opened dbDynaset with the
' dbDenyWrite option, the recordset then cannot be re-opened until the
' first is closed. Error 3008 is trapped, sent to a timer and the
' process is repeated until the recordset is able to be opened or a
' timeout is reached.
'
'
'
'Autonumber table must contain only one field and one record or less
'The field datatype of the autonumber table must be Long Integer
'
'GetAutoNum returns string datatype, as all custom autonumers are stored
'in the db as strings
'
'Requires Sleep API (declared publicly in a seperate module, though
'can be declared privately in this module

Option Compare Database
Option Explicit


Public Enum eAutoNumbers
dsAutoNumPurchaseOrder = 0
dsAutoNumQuote = 1
dsAutoNumPackingSlip = 2
dsAutoNumInvoice = 3
End Enum

'==============================================================================
' NAME: GetAutoNum
' DESC: Retrieves the next Autonumber for the given type
' Returns String converted from the next numbe
'==============================================================================
'ErrStrV3.00
Public Function GetAutoNum(lANum As eAutoNumbers) As String
On Error GoTo Error_Proc
Dim Ret As String
'=========================
Dim sTable As String 'table to look up from
'=========================

'get data required to look up number
Select Case lANum
Case dsAutoNumPurchaseOrder
sTable = "tblANumPOs"
Case dsAutoNumQuote
sTable = "tblANumQuotes"
Case dsAutoNumPackingSlip
sTable = "tblANumPackSlips"
Case dsAutoNumInvoice
sTable = "tblANumInvoices"
End Select


Ret = pfGetNextNum(sTable)
'=========================
Exit_Proc:
If Len(Ret) = 0 Then
MsgBox "An unexpected error has occured while retrieving" & vbCrLf & _
"the next available number. Please try again" & vbCrLf & _
"and notify your Administrator if the problem persists." _
, vbCritical + vbOKOnly, "Error!"
End If
GetAutoNum = Ret
Exit Function
Error_Proc:
MsgBox "Error: " & Trim(Str(Err.Number)) & vbCrLf & _
"Desc: " & Err.Description & vbCrLf & vbCrLf & _
"Module: Module1, Procedure: GetAutoNum" _
, vbCritical, "Error!"
Resume Exit_Proc
Resume
End Function

'==============================================================================
' NAME: pfGetNextNum
' DESC: gets the next increment from an autonumber table
' Returns ZLS on errors, table not availabl
'==============================================================================
'ErrStrV3.00
Private Function pfGetNextNum(TableName As String) As String
On Error GoTo Error_Proc
Dim Ret As String
'=========================
Const pcTimeOut = 3500 '3.5 seconds timeout
Const pcWaitIncr = 100 'wait increment between tries
Dim bDone As Boolean 'flag if the operation completed
Dim iCounter As Integer 'counter for intcrements
Dim lNextNum As Long 'next number
Dim rs As DAO.Recordset
'=========================

'explicitly initialize
bDone = False
iCounter = 0
lNextNum = 0

'loop while the done flag is false and the counter is under the TimeOut
While (Not bDone) And (iCounter < pcTimeOut)

On Error Resume Next

Set rs = CurrentDb.OpenRecordset(TableName, dbOpenDynaset, dbDenyWrite)

If Err.Number = 3008 Then 'rs is locked

'clear the error and reset handler
Err.Clear
On Error GoTo Error_Proc
Set rs = Nothing

'sleep the code and increment the counter
Sleep (pcWaitIncr)
iCounter = iCounter + pcWaitIncr

ElseIf Err <> 0 Then

'an actual error occured
GoTo Error_Proc

Else

'reset the handler
On Error GoTo Error_Proc

If rs.RecordCount = 0 Then

'no current records... start at 1
lNextNum = 1

With rs
.AddNew
rs(0) = lNextNum
.Update
End With

'flag that we're done here
bDone = True

Else 'there's an existing record

'current record availble, increment by 1
rs.MoveFirst
lNextNum = rs(0) + 1

With rs
.Edit
rs(0) = lNextNum
.Update
End With

'flag that we're done here
bDone = True

End If

'close rs
rs.Close
Set rs = Nothing

End If

Wend

'return ZLS on errors, 0 is nonvalid return...
Ret = IIf(lNextNum = 0, "", Trim(Str(lNextNum)))
'=========================
Exit_Proc:
pfGetNextNum = Ret
Exit Function
Error_Proc:
Ret = ""
Set rs = Nothing
MsgBox "Error: " & Trim(Str(Err.Number)) & vbCrLf & _
"Desc: " & Err.Description & vbCrLf & vbCrLf & _
"Module: Module1, Procedure: pfGetNextNum" _
, vbCritical, "Error!"
Resume Exit_Proc
Resume
End Function
'============END CODE=============




--
Jack Leach
www.tristatemachine.com

"I haven''t failed, I''ve found ten thousand ways that don''t work."
-Thomas Edison (1847-1931)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top