The NotesDocumentCollection class has a few attributes that can be rather annoying:-
- The Notesdatabase object from which the NotesDocumentCollection is derived must remain in memory. e.g. A function that returns a NotesDocumentCollection will return an empy object if the NotesDatabase used to create the collection is local to the function. The NotesDatabase must be either passed as a parameter to the function, or be a global variable.
- Documents can only be added to the collection if they are from the same NotesDatabase from which the NotesDocumentCollection was created.
As a way around these issues the following represents an alternate way to represent a document collection. This is to store the collection as an array of NotesURLs. Not only does this technique remove the issues above but it also has the added advantage that it can be easily serialized. e.g. Being an array of strings the values can be easily stored as a field in a Notes document or can be passed back from a Web service. Of course your application must understand this representation of a document collection and how to process such a collection.
The following function demonstrates how an array of NotesURLs can be created from an Notesdocumentcollection:-
' */
' * Convert collection to an array of NotesURLs
' */
Function toArray(iDocumentCollection As NotesDocumentCollection) As Variant
Dim Doc As NotesDocument
Dim Results As Variant
Set Doc = iDocumentCollection.GetFirstDocument
While Not Doc Is Nothing
If Isarray(Results) Then Redim Preserve Results(Ubound(Results)+1) Else Redim Results(0)
Results(Ubound(Results)) = Doc.NotesURL$
Set Doc = iDocumentCollection.GetNextDocument(Doc)
Wend
toArray = Results
End Function
I favor the NotesURL format for representing a document because of the ease in which it can be converted to a NotesDocument via the NotesSession.Resolve method. Unlike a NotesDocumentCollection the array of NotesURLs is enumerated so requires less code to process:-
Forall DocURL In Results
Set Doc = Session.Resolve(DocURL$)
' code to process document
End Forall
The DominoDocumentCollection class within the .Domino Framework provides a toArray method to convert an existing NotesDocumentCollection. The constructor of the DominoDocumentCollection class is able to accept an array of NotesURLs to create a NotesDocumentCollection from the NotesURLs. Note: If documents exist in multiple databases only documents from the first database found are added.
Comments (2)
"The NotesDatabase must be either passed as a parameter to the function, or be a global variable."
I emailed you the technique that I've been using to deal with this
for nearly a decade. It's an approach that has done wonders for the
performance of my code in multi-NSF applications.
Here's what I emailed you. Apparently I don't have a current mail address for you...
There's a trick I do in all of my classes to address this that I'll
share with you. I'll usually have an Initialize event that just
sets the global session to a new instance. You could also use a
factory class to force singleton behavior or something. I'm sure
there's all kinds of ways to tune it.
At first, I didn't have the handle limit, but then I found when I
was using the cache to run through reporting on multiple NSFs on a
server, I'd thrash the memory something fierce.
Anyway, net result is that instead of going
NotesSession.getDatabase (server, filepath), I just do
globalSession.dbCache(filepath). Then it stays in scope until I
need to specifically release it.
I started doing this about 8 years ago, and I've seen
order-of-magnitude performance improvements in some cases, because
getting db handles isn't just a scoping problem, it's also a REALLY
expensive operation.
Dim globalSession as superSession
Private Const DBCACHE_HANDLE_LIMIT = 100
Class superSession
Public dbMap List As String
'A list of specific shorthand maps to other NSFs that might be
integral to the application, such as a keywords.nsf lookup.
'You'd populate it with something like dbMap("keywords") =
"core\keywords.nsf"
'originally private, but since we're going to use it for our cache
lock criteria, we want it setable by the application
Private sess As NotesSession
Private dbCache List As NotesDatabase
Private server As String
Private sourcePath As String
Private dbCount As Integer
Sub New
Set Me.sess = New NotesSession
Set Me.sourceDb = Me.sess.currentDatabase
Me.sourcePath = Replace(Me.sourceDb.filePath,
Me.sourceDb.fileName, "")
Me.server = Me.sourceDb.server
End Sub
Property Get dbCache (dbKey As String) As NotesDatabase
Dim targServer As String
Dim targPath As String
If Me.dbCount => DBCACHE_HANDLE_LIMIT Then
Forall cache In Me.dbCache
'why a forall?
'because we want to roll off the oldest one
'if it's in use still, we'll pick it up again, and roll off the
next
'that sure beats maintaining some sort of "last accessed" index
:-P
'however, if the db is in the dbMap, we'll consider the cache
LOCKED
'because if it's an integral enough part of the app to get
documented like that,
'then it's probably not a transient member of the cache.
If Not Iselement(dbMap(Listtag(cache))) Then
Erase cache
Exit Forall
End If
'note: if we did want to start maintaining a last accessed index
'it wouldn't be particularly difficult.
End Forall
End If
If Not(Iselement(Me.dbCache(dbKey))) Then 'if it's not in the
cache, then we'll get a handle on it
If Instr(dbKey, "!!") Then 'bang bang to specify a different
server (I never do this, but it's an option)
targServer = Left$(dbKey, Instr(dbKey, "!!")-1)
targPath = Mid$(dbKey, Instr(dbKey, "!!")+2)
Else
targServer = Me.server
targPath = dbKey
End If
If Iselement(dbMap(dbKey)) Then 'if it's in the map, then we have
a shorthand to it.
Set Me.dbCache(dbKey) = Me.sess.getDatabase(targServer,
dbMap(targPath))
Else
'first check in the matching directory structure, then go from
root...
Set Me.dbCache(dbKey) = Me.p_sess.getDatabase(targServer,
Me.sourcePath + targPath)
If Not(Me.dbCache(dbKey).isOpen) Then
Set Me.dbCache(dbKey) = Me.sess.getDatabase(targServer, targPath)
End If
End If
Else
' Print "db" + dbKey + " cached!"
End If
Set dbCache = Me.dbCache(dbKey)
Me.dbCount = Me.dbCount+1
End Property
End Class