Skip Navigation
Details
Author: Jake Howlett
Date: Fri 8 Feb 2008

Permalink

Comments / Add / Subscribe

« New Improved Version 1.1 of WebSession Class | Blogs | Flyout Admin Menu: Coding the Server-Side »

Odd Behaviour of LotusScript Function Not Returning NotesDocument

I know I promised the flyout download today but that needs more work and I don't have the time today. Sorry.

Instead I need to ask for assistance. Something simple is driving me crazy and you're my only hope.

I have written a simple function in my global script library that I can use in all my agents to get the current user's Person document from the NAB. It's called GetUserDocument and looks like this:

Function GetUserDocument(username as String) As NotesDocument
 Dim NAB As New NotesDatabase(web.database.Server, "names.nsf")
        
 If NAB.IsOpen Then
        Dim UserView as NotesView
        Set UserView = NAB.GetView("($VIMPeople)")
                
        If UserView Is Nothing Then
                Set GetUserDocument = Nothing
                Exit Function
        End If
 Else
        Set GetUserDocument = Nothing
        Exit Function
 End If
        
 'TESTING
 Dim tmp As NotesDocument
 Set tmp = UserView.GetDocumentByKey(username, True)
 print tmp.FullName(0) 'This prints ok and finds right user!
 'END TESTING
        
 Set GetUserDocument = UserView.GetDocumentByKey(username, True)
End Function

To test the function I've written an agent which contains the code below:

Dim UserDoc As NotesDocument
Set UserDoc = GetUserDocument(web.user.Canonical)

If UserDoc Is Nothing Then
        Print "Can't find it!"
Else
        Print "Found it!"
End If

The trouble is that it doesn't seem to work. What I get printed back is "Jake Howlett Can't find it!" which suggests that the function is getting to the end and the "tmp" document is found ok, but when the document is returned to the calling agent is becomes "nothing". What the...

I have other functions in the library that return NotesDocuments and they work fine. The only difference I can see is that they are in the same database, whereas, in this case, they are not. Is that some weird limitation I don't know about?

Comments

Sandra Noronha (Fri 8 Feb 2008 09:36 AM) website / e-mail

That's because you're declaring the NAB db object inside the called function.. when you return the document to the calling script it's parent database object is gone, therefore the document is lost too.

The Notes Forum post on this: {Link}

Jake Howlett (Fri 8 Feb 2008 09:42 AM)

Aha. You won't believe this, but that did occur to me. Honestly. I just didn't think to test it.

Looks like my global "web" object needs a new AddressBook property...

Thanks Sandra!

Sandra Noronha (Fri 8 Feb 2008 09:47 AM) website / e-mail

I've had my share of head scratching because of this one. I'm glad I could help. ;)

Pedro Quaresma (Fri 8 Feb 2008 09:48 AM) e-mail

Does the result change if you replace

Set GetUserDocument = UserView.GetDocumentByKey(username, True)

with

Set GetUserDocument = tmp

?

Jake Howlett (Fri 8 Feb 2008 09:57 AM)

Maybe not as simple as i'd thought. I tried to add the NAB variable to my "web" global WebSession object, like so:

Class WebSession

Public NAB As NotesDatabase

Sub new

Set Me.NAB = New NotesDatabase(Me.database.Server,"names.nsf")

End Sub

End Class

set web= new WebSession()

But using web.NAB in the GetUserDocument still doesn't work. Does the DB have to be an actual global variable in its own right?

Jake

Jake Howlett (Fri 8 Feb 2008 10:00 AM)

Scratch that last comment. It does work from the global web object.

Werner Götz (Fri 8 Feb 2008 10:05 AM) e-mail

In my code I always use a global list for caching NotesDatabases, NotesViews ...

This has at least 2 advantages: It is faster when the database or view is used later again AND you have no problems with the Lotusscript memory management which in your case deletes the instance of the NotesDatabase because it is not more used and thereby deletes all "depending" objects too.

In your case I would have (code not tested!)

Dim gCacheObjects List As Variant

Function NABDb As NotesDatabase

Dim id

id = "NABDb"

If Not Iselement(gCacheObjects(id)) Then

Set gCacheObjects(id) = New NotesDatabase(web.database.Server, "names.nsf")

End If

If Not gCacheObjects(id).IsOpen Then Error 32000, "NAB not available"

Set NABDb = gCacheObjects(id)

End Function

Function NABView(viewname) As NotesView

Dim id

id = "NABView#" & Ucase(viewname)

If Not Iselement(gCacheObjects(id)) Then

Set gCacheObjects(id) = NABDb.GetView(viewname)

End If

If gCacheObjects(id) Is Nothing Then Error 32000, "NAB-View <" & viewname & "> not available"

Call gCacheObjects(id).Refresh

Set NABView = gCacheObjects(id)

End Function

Function GetUserDocument(username As String) As NotesDocument

Dim id

id = "UserDocument#" & Ucase(username)

If Not Iselement(gCacheObjects(id)) Then

On Error Resume Next

Err = 0

Set gCacheObjects(id) = NABView("($VIMPeople)").GetDocumentByKey(username, True)

If Err <> 0 Then

Set gCacheObjects(id) = Nothing

Err = 0

End If

End If

Set GetUserDocument = gCacheObjects(id)

End Function

Julian Robichaux (Fri 8 Feb 2008 10:32 AM) website

I run into that one all the time. I can never seem to remember what the problem is from one time to the next either.

Oh, and you might want to use NotesSession.AddressBooks to get the NAB instead. Just in case the database isn't names.nsf (happens sometimes).

Harkpabst Meliantrop (Fri 8 Feb 2008 10:38 AM) e-mail

Since Jake has his WebSession class already, making NAB a property of that session sounds like a much better idea than dealing with ugly global variables.

Preferably use lazy initialization on that one.

Additionally, I fully agree to Julian. To both parts of his posting ... ;-)

Jim Knight (Fri 8 Feb 2008 12:30 PM) website / e-mail

I think it works if you pass the db with it in the function as another object.

Function GetUserDocument(NAB as notesdatabase, username as String) As NotesDocument

Tommy Valand (Fri 8 Feb 2008 01:08 PM) website / e-mail

This is off-topic, but why:

Set GetUserDocument = Nothing ?

The result of the Function would still be Nothing if you just did:

If UserView Is Nothing Then Exit Function

Jake Howlett (Fri 8 Feb 2008 01:22 PM)

Dunno Tommy. Inexperience I guess. I assumed you had to have a line that set the result to the name of the function at some point in the proceedings. I'll take you word for it that you don't and maybe change it...

Tommy Valand (Fri 8 Feb 2008 01:43 PM) website / e-mail

Simple way to verify for yourself:

Function test As NotesDocument

End Function

--

If test() Is Nothing Then

Msgbox "Test is Nothing"

Else

Msgbox "Test is something"

End If

--

In my limited experience, a Function always returns an uninitialized variable of it's return type.

Function test As String

End Function

test() -> ""

Dim testStr As String

testStr -> ""

Andre Guirard (Fri 8 Feb 2008 09:37 PM) website / e-mail

I have to admit that I am lazy and often let functions return the default (Nothing, 0, False, "" or Empty, depending on datatype). However, setting it explicitly does make for better readability. Not everyone has these memorized.

Kirk Stoner (Sat 9 Feb 2008 01:19 AM) e-mail

There are three very simple lines of code that will make this work for you:

1 and 2: In the Declarations of the script library:

Public sess as NotesSession

Public dbThis as NotesDataBase

# 3 - Declare your function as public (you should specify "Option Public" in script lib options if it is not already there)

Public Function GetUserDocument(username as String) As NotesDocument..

As the session and current database are very commonly used in many global functions and subs, I take this a step further by declaring both session and database globally and then intialiizing them in the Intialize Sub of my global script lib. This negates the need to intialize them any number of different places or having to test them for Nothing over and over (testing for isOpen and isValid is recommended in your Initialze sub following the set statements), they are there ready to use by any function or sub within the library itself as well as any form, agent or action that 'use's that script lib

defaultLib

Options

Option Public

Declarations

Public sess as NotesSession

Public dbThis as NotesDatabase

Initialize

Set sess = New NotesSession

Set dbThis = sess.CurrentDatabase

If not ( dbThis.IsOpen ) Then

....error handling...

ElseIf not ( dbThis.IsValid ) Then

....error handling...

End If

ANY code that uses defaultLib can reference sess or dbThis objects without worry as to whether or not they are set

By declaring Function GetUserDocument as Public, the object it returns is available globally as well.

Explcit declaration and initialization or destruction of any variable definitely falls under best practice guidelines.

So, I talked about the pros of this approach, here's the cons:

1) Any code that uses defaultLib, takes the performance hit of declaring and initializing those two class objects (sess and dbThis).

2) If this code breaks for any reason, any code that uses is broken as well (we all know and respect the corruption factor of reusable code I'm sure).

Peace

Tony Palmer (Sat 9 Feb 2008 05:51 AM) website / e-mail

Hi Jake,

I just tried your code, as passing around notes document objects without the database being in scope is something I've always done - and not had a problem with. I was thinking all day - have I been keeping the db's in scope by luck...nah, surely not. So with nothing better to do on a Saturday night, than settle my curiosity - I've imported your code into ND8 and it works as expected. I did make a couple of changes like adding the Option Public in the Library - if you don't you should get an error in the Agent during compile time. The other change is that I switched out the username to be a value. Then finally, I checked that the documents UNID was the same for both getdocumentbykey calls. I'd normally would have done what pedro suggested, rather than calling the getdocumentbykey multiple times.

You've probably done this already, but try setting the username to a literal value to see if you get any different results, then try a local names.nsf.

I've exported the code below, but I can send you a zipped copy of the db - if you want.

Not sure if this helps you or not - but I can sleep easy tonight :-)

'jsJake:

Option Public

Dim UserView As NotesView

Dim tmp As NotesDocument

Function GetUserDocument(username As String) As NotesDocument

Dim NAB As New NotesDatabase("", "names.nsf")

If NAB.IsOpen Then

Set UserView = NAB.GetView("($Users)")

If UserView Is Nothing Then

Set GetUserDocument = Nothing

Exit Function

End If

Else

Set GetUserDocument = Nothing

Exit Function

End If

'TESTING

Set tmp = UserView.GetDocumentByKey(username, True)

Print tmp.FullName(0) 'This prints ok and finds right user!

Print "Docid :"+tmp.UniversalID

'END TESTING

Set GetUserDocument = UserView.GetDocumentByKey(username, True)

End Function

'Development \ Testing Jake:

Use "jsJake"

Sub Initialize

Dim UserDoc As NotesDocument

Set UserDoc = GetUserDocument("jake")

If UserDoc Is Nothing Then

Print "Can't find it!"

Else

Print "Found it!" + Userdoc.Fullname(0)

Print "Doc id:"+Userdoc.UniversalID

End If

End Sub

Sub Terminate

End Sub

Stan Rogers (Sun 10 Feb 2008 05:49 AM) website / e-mail

@Tony -- UserView lives outside of the function in your example, so the reference chain is alive outside of the function.

Jake Howlett (Sun 10 Feb 2008 06:50 AM)

How come the chain can survive if the View is global and the database isn't? If that can happen why can't the document survive too? Odd.

Tony Palmer (Sun 10 Feb 2008 06:00 PM) website / e-mail

@Stan

My mistake, copied the wrong lss. However,

I believe that once you have a handle to the object then you can pass that object to other functions, regardless of it's relationship in the class hierarchy (I'm not sure about this reference chain).

To check my sanity I tried the following - which also worked.

'jsJake:

Option Public

Function GetUserDocument(username As String) As NotesDocument

Dim NAB As New NotesDatabase("", "names.nsf")

Dim UserView As NotesView

Dim tmp As NotesDocument

If NAB.IsOpen Then

Set UserView = NAB.GetView("($Users)")

If UserView Is Nothing Then

Set GetUserDocument = Nothing

Exit Function

End If

Else

Set GetUserDocument = Nothing

Exit Function

End If

'TESTING

Set tmp = UserView.GetDocumentByKey(username, True)

Print tmp.FullName(0) 'This prints ok and finds right user!

Print "Docid :"+tmp.UniversalID

'END TESTING

' REMOVAL OF THE REFERENCE CHAIN

Print ("removing database from scope")

Set NAB = Nothing

' Delete NAB - this crashes the client

Print ("removing view from scope")

Delete UserView

Print tmp.FullName(0) 'This prints ok and finds right user!

Print "Docid :"+tmp.UniversalID

Set GetUserDocument = tmp

End Function

So even when I explicitly set the view and database to nothing it to nothing seems to work and keep the document object. Interestingly, I tried both delete and setting to nothing and deleting the NAB object crashes the 8.0.0 client.

Tony Palmer (Sun 10 Feb 2008 06:43 PM) website / e-mail

In the Designer Help - LotusScript/COM/OLE classes in the 'Using an Object' the following is at the bottom.

"When you delete a Domino object, any contained objects are lost. For example, if you delete or close a NotesDatabase object, any NotesDocument objects in that database to which you refer are deleted."

So I went back to the code. In the list of variables after I set the db to nothing and delete the view they are both empty. However, the document still has a reference to its parent database, that you can then browse the views and isOpen = true. Strange.

Remko (Mon 11 Feb 2008 02:15 AM)

Try this:

public Class NAB

Private vw As NotesView

Private db As NotesDatabase

Private isValid As Boolean

Sub new (sSV As String, sDB As String)

Set db = New NotesDatabase(sSV, sDB)

If db.IsOpen Then

Set vw = db.GetView("($VIMPeople)")

isValid = True

End If

End Sub

Function getUserDoc(sUsername As String) As NotesDocument

If Me.isValid Then

Set getUserDoc = vw.GetDocumentByKey( sUsername, True )

Else

Set getUserDoc = Nothing

End If

End Function

End Class

Jake Howlett (Mon 11 Feb 2008 03:07 AM)

Tony. I think the big difference is that you're running the code locally in the client and I'm talking solely about WQ agents on the web. That's probably why we're seeing different results.

Add your response here:

Name *:
E-mail:
Protected from spambots!
Website:
rel="nofollow"
Remember My Details
Comment *:
HTML is not allowed!

Note: This blog entry is more than two weeks old so your comment, as an anti-spam measure, will be sent for approval.