How to avoid entering a duplicate entry in a database? - vb6

I have a VB6 application where I am trying to avoid inserting a duplicate entry of a PIN number. But my code is always saving the duplicate entry.
Here is my current code:
Public Function IsPIN_NOExists(ByVal TableName As String, _
ByRef EmployeeCode As String, ByVal FieldName As String, ByVal DataToCheck As String, _
Optional ByVal CodeFieldName As String, Optional ByVal CodeFieldValue As String) As Boolean
TableName = UCase$(Trim$(TableName))
EmployeeCode = Trim$(EmployeeCode)
On Error GoTo ErrorHandle
Dim lstrSQL1 As String
Dim lrsTemp1 As ADODB.Recordset
lstrSQL1 = " Select " & FieldName & " from " & TableName & " Where PIN_NO =" & DataToCheck & ""
If Len(Trim$(CodeFieldName)) <> 0 And Len(Trim$(CodeFieldValue)) <> 0 Then
lstrSQL1 = lstrSQL1 & " AND " & CodeFieldName & " <> '" & CodeFieldValue & "'"
End If
Set lrsTemp1 = cObjDBConn.ExecuteSQL(lstrSQL1)
If lrsTemp1 Is Nothing Then
IsPIN_NOExists = False
ElseIf Not (lrsTemp1.BOF And lrsTemp1.EOF) Then
IsPIN_NOExists = True
lrsTemp1.MoveFirst
EmployeeCode = lrsTemp1.Fields("EMPLOYEE_CODE")
MsgBox (EmployeeCode)
ElseIf lrsTemp1.RecordCount = 0 Then
IsPIN_NOExists = False
Else
IsPIN_NOExists = False
End If
If lrsTemp1.State = adStateOpen Then lrsTemp1.Close
Set lrsTemp1 = Nothing
Exit Function
ErrorHandle:
IsPIN_NOExists = False
End Function
And here is my calling code for this function:
If Trim$(TxtPINno.text) <> "" And Trim$(TxtPINno.text) <> "-" Then
'If gObjValidation.IsCodeExists(fstrTableName, gEmployerCode, "PIN_NO", Trim$(TxtPINno.text)) = True Then
If gobjValidation.IsDescriptionExists(fstrTableName, gEmployerCode, "PIN_NO", Trim$(TxtPINno.text), "EMPLOYEE_ID", Val(txtEmpCode.Tag)) = True Then
If gobjValidation.IsPIN_NOExists(fstrTableName, gEmployeeCode, "EMPLOYEE_CODE", _
Trim$(TxtPINno.text)) = True Then
MsgBox (gEmployeeCode)
Call MessageBox("This PIN Number is already existing for another employee. Cannot enter duplicate number!", OKOnly, Information, DefaultButton1, Me.Caption)
sstInformationTab.Tab = 0
If TxtPINno.Enabled = True Then TxtPINno.SetFocus
CheckAllValidations = False
Exit Function
End If
End If
End If
How can I fix this code to avoid entering a duplicate entry?
Edit: Adding in ExecuteSQL function code
Public Function ExecuteSQL(ByVal SQLQueryStatement As String) As ADODB.Recordset
On Error GoTo ErrorHandler
Dim lrs As ADODB.Recordset
cintDBHitCtr = cintDBHitCtr + 1
Set lrs = DBConnection.Execute(SQLQueryStatement, , adCmdText)
Set lrs.ActiveConnection = Nothing
Set ExecuteSQL = lrs
Set lrs = Nothing
Exit Function
ErrorHandler:
Set ExecuteSQL = Nothing
Call TrapDatabaseError(SQLQueryStatement, DBConnection.Errors(0), cDBType)
End Function

It looks like you've thrown extra logic into your function to try to fix your problem. This:
If lrsTemp1 Is Nothing Then
IsPIN_NOExists = False
ElseIf Not (lrsTemp1.BOF And lrsTemp1.EOF) Then
IsPIN_NOExists = True
lrsTemp1.MoveFirst
EmployeeCode = lrsTemp1.Fields("EMPLOYEE_CODE")
MsgBox (EmployeeCode)
ElseIf lrsTemp1.RecordCount = 0 Then
IsPIN_NOExists = False
Else
IsPIN_NOExists = False
End If
Stripped of all extraneous logic, can be replaced with this:
With lrsTemp1
IsPIN_NOExists = Not (.BOF And .EOF)
If IsPIN_NOExists Then
EmployeeCode = lrsTemp1.Fields("EMPLOYEE_CODE")
End If
End With
(Presumably, all your MsgBoxes are to try to troubleshoot what's wrong, so they can be left out.)
Now, the logic in your function, in plain English, is "If the PIN exists in the database, then return true." Looking at your calling code, your logic in plain English is "If the function returns true then tell the user that the PIN already exists." Since this logic is correct, it follows that your error is somewhere else in your code.
Your error is very probably somewhere in the code itself. Disable your error handler (just comment out the "on error" statement) and you will probably find that you get an error on another line. As a general rule, don't put error handlers in your code until you are sure that it works as intended. Error handlers are for users, not developers.
The way that you have your error handler set up, if you get an error your code will keep adding the duplicate error, which is the problem that you describe. So it seems likely that that is your problem. Now, without intending to be offensive, I must also tell you that this is a very bad (terrible, even) error handling design. Your logic in plain English is "if there is an error, tell the calling code that you didn't find a duplicate key." That's not so good, is it? :) For example, if you have an error in your SQL statement, then your code will insert the duplicate PIN.
Also, your function sounds like "PIN doesn't exist", when you are returning true if it does exist. Consider renaming it to something like "PIN_IsDuplicate" or better (that is, more consistent with standard naming conventions) "IsDuplicatePIN".
***** EDIT *****
Ok, I believe I have your solution. If you are too conscientious about "cleaning up" your open object references, you run the risk of dropping them before you are finished using them. In this case, you are attempting to break the active connection to your recordset, because you are under the incorrect impression that the connection is only necessary when you first pull the data from the recordset. While this is true in the .Net world, it is only true in the VB6 world if you specifically set up a "disconnected recordset."
Your problem is with this line of code:
Set lrs.ActiveConnection = Nothing
When you do this, you will get the error (if you disable your error handler) "Operation is not allowed when object is open." This means that you have to maintain your connection to the database as long as the recordset is open, which it is until you close it or until the function ends.
Tracing through your code, if you get an error on this line by trying to destroy the connection, then you call your error handler. The error handler tells the function to return a null object pointer to the calling function. So, in the calling function, this line of code:
Set lrsTemp1 = cObjDBConn.ExecuteSQL(lstrSQL1)
actually sets lrsTemp1 to Nothing. Therefore, the first condition of your If block is satisfied, and your function returns False as instructed. This gives the obscure behavior that you understandably found confusing.
So, here's what to take away from this. First, error handlers are for users, not for developers. They should be the last step in your coding process, because they turn runtime errors into logical errors which are harder to find. If you find that you have errors in code that has error handlers in place, the first step is to disable all error handlers. To do this, go to Tools/Options/General and select Break on All Errors. That will cause the runtime to ignore the error handlers.
Next, don't keep setting things to Nothing. A lot of misguided expertise in earlier versions of VB suggests that you need to do this. In truth, the VB runtime is much better at releasing object references that go out of scope than you are. It does so automatically in all but a few very well defined situations which you are unlikely to encounter. So, get rid of all of your "Set Object = Nothing" statements. You don't need them, and as you are finding out, they can cause errors.
One thing that it is helpful to do, however, is to explicitly close your connection (call the connection object's Close method) when you are done using it. Dropping the Recordset's ActiveConnection property doesn't close the connection, it just says that the recordset doesn't want to use it anymore. Also, if you quit using it by setting the connection variable to nothing, the server won't automatically be notified that the connection is closed. It will get around to figuring it out eventually, but you will save resources by doing it in your code.
Finally, get clearer on the different types of cursors. The default CursorType is adOpenForwardOnly, a cursor which only supports MoveFirst and MoveNext. This is the simplest one, which is appropriate for either selecting a single value (as you are) or iterating through a recordset from top to bottom (for example, when populating a listbox with the contents of the recordset). This cursor must remain connected to the database until you close the recordset, which is why you get an error when you try to drop the active connection.
If you want a recordset that you can still use after disconnecting from the database, you need a client-side recordset, and will need to set the CursorLocation property to adUseClient. This can only have a CursorType of adOpenStatic, which is a fully-traversable (supports moving backwards, forwards, by multiple records, etc.) non-updateable cursor--a "static" or "snapshot" cursor. (If you set the CursorLocation property to adUseClient, the CursorType will be automatically forced to adOpenStatic.)
If you want to do all this, you can't use the Execute method of the Connection object. Instead, you have to use the Recordset's Open method. Here's a bit of code that pulls the authors table from the SQL Server pubs sample database into a disconnected recordset and prints all the last names to the debug window:
Dim cn As ADODB.Connection
Dim rs As ADODB.Recordset
Set cn = New ADODB.Connection
cn.Open "Provider=SQLOLEDB.1;Persist Security Info=False;Data Source=.\SQLEXPRESS;Initial Catalog=pubs;Integrated Security=SSPI;"
Set rs = New ADODB.Recordset
With rs
.CursorLocation = adUseClient
.Open "select * from authors", cn, adOpenStatic
Set .ActiveConnection = Nothing
cn.Close
Do Until .EOF
Debug.Print .Fields("au_lname")
.MoveNext
Loop
.MoveFirst
End With

Related

How to fix Wscript.CreateObject returning empty when prefix is specified?

I'm trying to process event handlers for ADODB.Command object. When I specify a prefix, Wscript.CreateObject exits the function without issuing any error message. When the prefix is removed from Wscript.CreateObject, the ADODB.Command object is created.
Additionally when setting On Error Resume Next, Wscript.CreateObject returns and empty object.
Can someone please tell me what I'm doing wrong ?
Code:
DIM tsqlcmd
On Error Goto 0
SET tsqlcmd = WScript.CreateObject("ADODB.Command", "ehdlr_tsqlcmd_")
.....
Private Sub ehdlr_tsqlcmd_ExecuteComplete( RecordsAffected, pError, adStatus, pCommand, pRecordset, pConnection)
' place any code you desire here, for example
If adStatus = STATUS_adStatusOK Then
MsgBox( "tsqlcmd_ExecuteComplete Records affected = " & RecordAffected)
Else
MsgBox( "tsqlcmd_ExecuteComplete ExecuteComplete Status = " & adStatus)
End If
End Sub

VBScript to check for registry key doesn't work when "option explicit" set

Okay, my first post here so hopefully I don't screw up too badly and bring the whole Internet down on top of me...
I have a simple function to check whether or not a registry key exists. It works as I expect, until I set "option explicit". Then it doesn't. No errors, it just doesn't return a correct value and I cannot figure out why. Undoubtedly it's a stupid error, and more than likely I'll slink away in shame when you all point out just how stupid it is, but as long you tell me what I'm doing wrong first it'll be worth it.
Here is the code:
'option explicit
if keyExists("HKEY_CURRENT_USER\Software\WritingInCode\Company\CreateCabinetDB\") then
wscript.echo "Key exists"
else
wscript.echo "Key doesn't exist"
end if
function keyExists(key)
dim objShell : set objShell = createObject("WScript.Shell")
on error resume next
entry = objShell.regRead(key)
select case err.number
case 0: keyExists = true
case else: keyExists = false
end select
err.clear
set objShell = nothing
end function
Alright, here is what is happening.
You set option explicit so that when a non-declared variable is encountered, the interpreter shall raise an error.
Before you reach the first non-declared variable, entry, you switch off error handling with on error resume next.
Then you encounter entry and an error is raised, but not reported.
Execution resumes and the non-declared variable error is interpreted by the select case statement. And so keyExists always returns false.
Fix the problem by declaring entry before attempting to use it:
dim entry : entry = objShell.regRead(key)

ADO Recordset Losing Its Data When Passed To Several Functions In Sequence (Access VBA)

I have a function that returns an ADO recordset, this in turn is used to feed subsequent functions. The first time it's passed to one of these functions, it runs fine; but in the next function, the recordset is at EOF. I assumed that my recordset was being passed By Ref, so tried using MoveFirst: this gives me an error because the cursor type is defaulted as forward only.
I tried the following:
Set it the parameter as By Val; this had no effect.
Created a copy of the recordset and used the original in the first function and the copy in the second; still the same problem (second is saying EOF, which implies that it's empty rather than at EOF; I can't imagine that the two recordsets are connected in some way, but perhaps).
Changed my DB code to use a different cursor type (adOpenStatic): This would actually be more correct for my application, but it gave very weird results. Namely, in a query that worked fine before, after the change, the first field was not being returned! This is kind of important, so I couldn't continue down this route.
Any ideas?
Main DB query function: GetData. Takes SQL and list of varchar parameters (if any) and returns recordset. It references dbOpen, which simply opens the connection with the supplied credentials.
Private Function GetData(ODBC As DBConnection, ByVal QuerySQL As String, Optional Parameters As Collection) As ADODB.Recordset
Dim DB As ADODB.Connection
Dim Query As ADODB.Command
Dim Parameter As ADODB.Parameter
Set DB = New ADODB.Connection
If dbOpen(DB, ODBC) Then
Set Query = New ADODB.Command
Query.ActiveConnection = DB
Query.CommandText = QuerySQL
If Not Parameters Is Nothing Then
For Each param In Parameters
Set Parameter = Query.CreateParameter(, adVarChar, adParamInput, Len(param), param)
Query.Parameters.Append Parameter
Next
Set Parameter = Nothing
End If
Set GetData = Query.Execute
' Uncommenting this seems to reset the data in GetData...
' A bit of a memory leak, but we assume VBA will take care of it :P
'dbClose DB
Else
MsgBox "Cannot connect to the database.", vbExclamation
Set GetData = Nothing
End If
End Function
I then do something like this:
Dim myRecords as ADODB.Recordset
' For the sake of argument, we assume this returns a nontrivial recordset
Set myRecords = GetData(someDSN, someSQL, someParameters)
Debug.Print myRecords.EOF ' Returns False
ID = writeHeader(myRecords)
Debug.Print myRecords.EOF ' Returns True
writeData ID, myRecords ' Breaks because at EOF
The writeHeader function will go through each record in the recordset, using MoveNext. However, if I pass myRecords by value or as a copy, then we still get the EOF problem. If I add MoveFirst at the end of the record iteration code in writeHeader, it will complain that I can't move to the beginning of such a recordset. If I change my GetData function so that Set GetData = Query.Execute is changed to:
Set GetData = New ADODB.Recordset
GetData.CursorType = adOpenStatic
GetData.Open Query
Then, when a query in my code is run -- which hasn't changed -- the first field is not a part of the recordset (!?) Say, for example, myRecords!ID is the first field: if this is referenced, I'll get a warning that said item isn't a member of the records (and it's not an enumerated member, either; so myRecords.Fields(1) just returns the next field in the query).
With a useful tip from #TimWilliams, I solved this by changing my GetData function to the following:
Private Function GetData(ODBC As DBConnection, ByVal QuerySQL As String, Optional Parameters As Collection) As ADODB.Recordset
Dim DB As ADODB.Connection
Dim Query As ADODB.Command
Dim Parameter As ADODB.Parameter
Dim Output As ADODB.Recordset
Set DB = New ADODB.Connection
If dbOpen(DB, ODBC) Then
Set Query = New ADODB.Command
Query.ActiveConnection = DB
Query.CommandText = QuerySQL
If Not Parameters Is Nothing Then
For Each param In Parameters
Set Parameter = Query.CreateParameter(, adVarChar, adParamInput, Len(param), param)
Query.Parameters.Append Parameter
Next
Set Parameter = Nothing
End If
Set Output = New ADODB.Recordset
Output.CursorType = adOpenStatic
Output.CursorLocation = adUseClient
Output.Open Query
Else
MsgBox "Cannot connect to the database.", vbExclamation
Set Output = Nothing
End If
Set GetData = Output
End Function
Then, before cursoring through the recordset, I use MoveFirst.

vbscript: test for existence of a column in a recordset

Bah, vbscript.
I'm trying to figure out how to get this statement to work:
if (not rsObject("columnNameThatDoesntExist") is nothing) then
' do some stuff
end if
' else do nothin
Where rsObject is a RecordSet and columnNameThatDoesntExist is ... well you know. I'm looking for something like rsObject.Columns.Contains(string). But of course can't find it.
Edit: Looks like looping rsObject.Fields is an option, is that the only way to do this?
I am sure you have your reasons, but if you don't know what fields you are calling back from your database, you could always use On Error Resume Next and On Error Goto 0 to ignore the tossed error. Seems like a bad way to me, but it would work
blnItWasntThere = True
On Error Resume Next
If (rsObject("columnNameThatDoesntExist") <> "") Then
blnItWasntThere = False
...
...
...
End If
On Error Goto 0
If blnItWasntThere Then
'handle this error'
End If
But with that said, I think you would be more worried about the mystery recordset you are getting back.
Or make your own function
Function ColumnExists(objRS, Column)
Dim blnOutput, x
blnOutput = True
On Error Resume Next
x = objRS(Column)
If err.Number <> 0 Then blnOutput = False
On Error Goto 0
ColumnExists = blnOutput
End Function
Either looooop and check if it's there, or just try to grab it:
Dim oRs:Set oRs = Nothing
On Error Resume Next
Set oRs = rsObject("columnNameThatDoesntExist")
On Error Goto 0
If Not rsObject("columnNameThatDoesntExist" Is Nothing Then
' ...
End If

Check if a record exists in a VB6 collection?

I've inherited a large VB6 app at my current workplace. I'm kinda learning VB6 on the job and there are a number of problems I'm having. The major issue at the moment is I can't figure out how to check if a key exists in a Collection object. Can anyone help?
My standard function is very simple. This will work regardless of the element type, since it doesn't bother doing any assignment, it merely executes the collection property get.
Public Function Exists(ByVal oCol As Collection, ByVal vKey As Variant) As Boolean
On Error Resume Next
oCol.Item vKey
Exists = (Err.Number = 0)
Err.Clear
End Function
#Mark Biek Your keyExists closely matches my standard Exists() function. To make the class more useful for COM-exposed collections and checking for numeric indexes, I'd recommend changing sKey and myCollection to not be typed. If the function is going to be used with a collection of objects, 'set' is required (on the line where val is set).
EDIT: It was bugging me that I've never noticed different requirements for an object-based and value-based Exists() function. I very rarely use collections for non-objects, but this seemed such a perfect bottleneck for a bug that would be so hard to track down when I needed to check for existence. Because error handling will fail if an error handler is already active, two functions are required to get a new error scope. Only the Exists() function need ever be called:
Public Function Exists(col, index) As Boolean
On Error GoTo ExistsTryNonObject
Dim o As Object
Set o = col(index)
Exists = True
Exit Function
ExistsTryNonObject:
Exists = ExistsNonObject(col, index)
End Function
Private Function ExistsNonObject(col, index) As Boolean
On Error GoTo ExistsNonObjectErrorHandler
Dim v As Variant
v = col(index)
ExistsNonObject = True
Exit Function
ExistsNonObjectErrorHandler:
ExistsNonObject = False
End Function
And to verify the functionality:
Public Sub TestExists()
Dim c As New Collection
Dim b As New Class1
c.Add "a string", "a"
c.Add b, "b"
Debug.Print "a", Exists(c, "a") ' True '
Debug.Print "b", Exists(c, "b") ' True '
Debug.Print "c", Exists(c, "c") ' False '
Debug.Print 1, Exists(c, 1) ' True '
Debug.Print 2, Exists(c, 2) ' True '
Debug.Print 3, Exists(c, 3) ' False '
End Sub
I've always done it with a function like this:
public function keyExists(myCollection as collection, sKey as string) as Boolean
on error goto handleerror:
dim val as variant
val = myCollection(sKey)
keyExists = true
exit sub
handleerror:
keyExists = false
end function
As pointed out by Thomas, you need to Set an object instead of Let. Here's a general function from my library that works for value and object types:
Public Function Exists(ByVal key As Variant, ByRef col As Collection) As Boolean
'Returns True if item with key exists in collection
On Error Resume Next
Const ERR_OBJECT_TYPE As Long = 438
Dim item As Variant
'Try reach item by key
item = col.item(key)
'If no error occurred, key exists
If Err.Number = 0 Then
Exists = True
'In cases where error 438 is thrown, it is likely that
'the item does exist, but is an object that cannot be Let
ElseIf Err.Number = ERR_OBJECT_TYPE Then
'Try reach object by key
Set item = col.item(key)
'If an object was found, the key exists
If Not item Is Nothing Then
Exists = True
End If
End If
Err.Clear
End Function
As also advised by Thomas, you can change the Collection type to Object to generalize this. The .Item(key) syntax is shared by most collection classes, so that might actually be useful.
EDIT Seems like I was beaten to the punch somewhat by Thomas himself. However for easier reuse I personally prefer a single function with no private dependencies.
Using the error handler to catch cases when the key does not exists in the Collection can make debugging with "break on all errors" option quite annoying. To avoid unwanted errors I quite often create a class which has the stored objects in a Collection and all keys in a Dictionary. Dictionary has exists(key) -function so I can call that before trying to get an object from the collection. You can only store strings in a Dictionary, so a Collection is still needed if you need to store objects.
The statement "error handling will fail if an error handler is already active" is only partly right.
You can have multiple error handlers within your routine.
So, one could accommodate the same functionality in only one function.
Just rewrite your code like this:
Public Function Exists(col, index) As Boolean
Dim v As Variant
TryObject:
On Error GoTo ExistsTryObject
Set v = col(index)
Exists = True
Exit Function
TryNonObject:
On Error GoTo ExistsTryNonObject
v = col(index)
Exists = True
Exit Function
ExistsTryObject:
' This will reset your Err Handler
Resume TryNonObject
ExistsTryNonObject:
Exists = False
End Function
However, if you were to only incorporate the code in the TryNonObject section of the routine, this would yield the same information.
It will succeed for both Objects, and non-objects.
It will speed up your code for non-objects, however, since you would only have to perform one single statement to assert that the item exists within the collection.
Better solution would be to write a TryGet function. A lot of the time you are going to be checking exists, and then getting the item. Save time by doing it at the same time.
public Function TryGet(key as string, col as collection) as Variant
on error goto errhandler
Set TryGet= col(key)
exit function
errhandler:
Set TryGet = nothing
end function
see
http://www.visualbasic.happycodings.com/Other/code10.html
the implementation here has the advantage of also optionally returning the found element, and works with object/native types (according to the comments).
reproduced here since the link is no longer available:
Determine if an item exists in a collection
The following code shows you how to determine if an item exists within a collection.
Option Explicit
'Purpose : Determines if an item already exists in a collection
'Inputs : oCollection The collection to test for the existance of the item
' vIndex The index of the item.
' [vItem] See Outputs
'Outputs : Returns True if the item already exists in the collection.
' [vItem] The value of the item, if it exists, else returns "empty".
'Notes :
'Example :
Function CollectionItemExists(vIndex As Variant, oCollection As Collection, Optional vItem As Variant) As Boolean
On Error GoTo ErrNotExist
'Clear output result
If IsObject(vItem) Then
Set vItem = Nothing
Else
vItem = Empty
End If
If VarType(vIndex) = vbString Then
'Test if item exists
If VarType(oCollection.Item(CStr(vIndex))) = vbObject Then
'Return an object
Set vItem = oCollection.Item(CStr(vIndex))
Else
'Return an standard variable
vItem = oCollection.Item(CStr(vIndex))
End If
Else
'Test if item exists
If VarType(oCollection.Item(Int(vIndex))) = vbObject Then
'Return an object
Set vItem = oCollection.Item(Int(vIndex))
Else
'Return an standard variable
vItem = oCollection.Item(Int(vIndex))
End If
End If
'Return success
CollectionItemExists = True
Exit Function
ErrNotExist:
CollectionItemExists = False
On Error GoTo 0
End Function
'Demonstration routine
Sub Test()
Dim oColl As New Collection, oValue As Variant
oColl.Add "red1", "KEYA"
oColl.Add "red2", "KEYB"
'Return the two items in the collection
Debug.Print CollectionItemExists("KEYA", oColl, oValue)
Debug.Print "Returned: " & oValue
Debug.Print "-----------"
Debug.Print CollectionItemExists(2, oColl, oValue)
Debug.Print "Returned: " & oValue
'Should fail
Debug.Print CollectionItemExists("KEYC", oColl, oValue)
Debug.Print "Returned: " & oValue
Set oColl = Nothing
End Sub
See more at: https://web.archive.org/web/20140723190623/http://visualbasic.happycodings.com/other/code10.html#sthash.MlGE42VM.dpuf
While looking for a function like this i designed it as following.
This should work with objects and non-objects without assigning new variables.
Public Function Exists(ByRef Col As Collection, ByVal Key) As Boolean
On Error GoTo KeyError
If Not Col(Key) Is Nothing Then
Exists = True
Else
Exists = False
End If
Exit Function
KeyError:
Err.Clear
Exists = False
End Function

Resources