logo

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

  1. 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}

    • avatar
    • 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!

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

  3. Does the result change if you replace

    Set GetUserDocument = UserView.GetDocumentByKey(username, True)

    with

    Set GetUserDocument = tmp

    ?

    • avatar
    • 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

    • avatar
    • Jake Howlett
    • Fri 8 Feb 2008 10:00 AM

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

  4. 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

  5. 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).

  6. 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 ... ;-)

  7. 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

  8. 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

    • avatar
    • 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...

  9. 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 -> ""

  10. 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.

  11. 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

  12. 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

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

    • avatar
    • 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.

  14. @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.

  15. 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.

    • avatar
    • 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

    • avatar
    • 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.

  16. This is an old blog I know, but I'm sooo glad I found this via google! The same problem was driving me crazy too!

      • avatar
      • gdelpierre
      • Thu 4 Mar 2010 05:23 AM

      You need to declare in global your database,session in which your return document is.

      For example i've a function that create a lock in an external DB, then another function that will check if a lock exist in this external DB (here it's sdoc variable in database DB). If my database DB is not declared in global, it doesn't work.

    • avatar
    • Sam Sirry
    • Wed 25 Aug 2010 11:55 PM

    Yes, Ferry. It's still happening in my part of the world too ;p in 2010!

    This used up 2 full days of head-scratching before I finally found this page through Google.

    Clearly I don't often work in multi-db set-ups this way...

    Thanks everyone!

    • avatar
    • Sam Sirry
    • Thu 26 Aug 2010 12:05 AM

    By the way, I tried this trick and some-how it worked like magic:

    set db = session.getdatabase(dbname)

    set doc = db.getdocumentbyid(doc_id) 'or use lookup or anything..

    url = doc.notesurl

    set db = nothing

    set doc = session.resolve(url)

    with a function containing the above code, I could return the document object and use it in another procedure.

    of course, this is not practical if, for example, you're working with a set of documents to return them in a documentcollection or an array.

  17. I tend to declare the database in the function as a Static.

    This seems to resolve the issue quite nicely.

    So your initial code would be something like

    Static NAB as NotesDatabase

    If NAB Is Nothing Then

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

    End If

    Not really sure about the memory impact, but it keeps the code nice and clean (no global variables)

Your Comments

Name:
E-mail:
(optional)
Website:
(optional)
Comment:


About This Page

Written by Jake Howlett on Fri 8 Feb 2008

Share This Page

# ( ) '

Comments

The most recent comments added:

Skip to the comments or add your own.

You can subscribe to an individual RSS feed of comments on this entry.

Let's Get Social


About This Website

CodeStore is all about web development. Concentrating on Lotus Domino, ASP.NET, Flex, SharePoint and all things internet.

Your host is Jake Howlett who runs his own web development company called Rockall Design and is always on the lookout for new and interesting work to do.

You can find me on Twitter and on Linked In.

Read more about this site »

More Content