.Net SDK
- not write a file to the file system but to a stream,
- fetch the document in chunks rather than waiting for the entire document, and
- start downloading part way through a file.
- the previous connection must be Disposed,
- the subsequent connection must have the same Database Id, work group server name and trusted user.
- HP.HPTRIM.DataPort.Common.dll
- HP.HPTRIM.SDK.dll
- System.Windows.Forms.dll
- System.Drawing.dll
- DoxyGen generated help files based on the SDK help strings,
- the SDK PDF converted to HTML, and
- a few new articles.
- ObjectSelector,
- PropertyEditor, and
- DesktopHelper
- challenges when using multiple domains,
- inability to plug in 3rd party authentication providers, and
- without careful design a requirement to allow all impersonated users access to the server operating system.
- the same account used to run the workgroup server, or
- an account registered in 'trusted server accounts' in Enterprise Studio.
- Everything (almost) in the SDK is a TrimMainObject, TrimChildObject, or property of one of these. I start typing in Visual Studio, up pops SecurityCaveat, a TrimMainObject.
- So, which mechanism relates it to a Record?
- Child list? No there is no relevant child list on the record object.
- Is there a search clause? No, you can search for records by caveat but not caveats by record.
- Maybe there is some special mechanism?
- Time to browse through the record properties, I spot Record.Security, but that is a string property, close but maybe not quite.
- Aha... Record.SecurityProfile, looks interesting. Does it contain a list of caveats? No, it has AddCaveat, RemoveCaveat but, wait in, here is something nearly as good...
- a first look at event processor add-ins,
- creating a ServiceAPI plugin
- there are many posts discussing WebDrawer customisation, here is one.
- write log entries from your code to verify where (or whether) your code us running,
- use the debugger to verify that you are at least getting to the first line of your code,
- start again, write the simplest possible add-in (like the one above) and bit by bit build your code up again until you work out where it is going wrong,
- double check you are using the correct version of the HPRM SDK, and
- don't forget to use the 'Test' button in HPRM Enterprise studio.
- Database,
- TrimMainObjectSearch, and
- DocumentStream.
Record Type sorting
The Content Manager user options allow you to sort Record Types, how do you achieve this using the SDK?
Form Definitions
For anyone wanting to build a user interface in Content Manager the FormDefinition class is essential. Some of the object types allow a form defintionto be created from a the FormDefintion contructor for new or existing objects. For records might also choose to use the RecordType.RecordPropertiesFormDefinition property.
Composing a search string
Search strings are required for searching in the web service and can be convenient in the .Net SDK, there are a few rules on how to compose these strings but if I need to compose a complex search to get baked into an application I like to use the Content Manager client to assist me.
Blazor
Blazor is a new Microsoft technology for building client web apps in C#. I have spent a total of 2 hours experimenting with it so here is my beginners intro. If you are interested and want to save the 2 hours I spent hacking together some CM SDK code you can find my project on the samples repo.
Stream Searching
TrimSearchDataStream provides a much faster though functionally limited search experience in Content Manager, see the sample code or watch the video.
Avoid setting DateLastUpdated when downloading a document
The default behaviour in Content Manager is to update the data LastUpdated when getting an electronic document from a Record. This can be problematic, particularly in cases where you want to get the document every time the Record changes. Thankfully this update can be override.
Copy your saved searches from database to database using Powershell
Maybe you have a testing or POC database in which you have a bunch of Saved Searches and you want to export these and import them to your production database. These Powershell scripts will export and import the search related properties, not the ACL of course.
Using TrimDateTime from a web service
When writing a web service that uses the CM SDK you need to take into account the fact that different users may be in different time zones. The SDK date/time object, TrimDateTime operates, buy default, in the same time zone as the operating system. To ensure you set dates correctly you need to convert it to operate in the same time zone as the user. The sample code demonstrates how to do this, for more detail have a look at this video.
Templated Title Record Add-in
This new sample add-in allows you to compose the Record Title from other properties and fields, replacing text in angle brackets with the matching property or field.
Parameterised Saved Searches
There are a few features in CM that you might not notice unless they are pointed out, one is parameterised saved searches. These can be used to provide users with pre-defined but customisable searches.
Create a private Record Type for your custom application
Use RecordType.ExternalId to make a Record Type and all of its Records private to your custom application.
Deleting in the ServiceAPI
Strangely enough there are multiple paths via which you might choose to delete a Record in the ServiceAPI. When looking back on how this choice was made I tend to agree with Elizabeth Bennett that a good memory is unpardonable, but, is it a problem that there are multiple ways to delete a Record?
Delete via Record post
The first method is to use a service action, that is to post JSON similar to that below to the Record endpoint. Not only does this attempt to delete the Record but also allows you to make updates to properties (and save) the Record prior to delete being called.
{ "Uri": 9000000544, "DeleteRecord": { "DeleteRecordDeleteContents": false } }
Delete via delete service
The other way to delete is simply to post to the delete service using a URL like this:
http://localhost/ServiceAPI/Record/rec_364/Delete
And the winner is…
Avoid the first option and use the second option. Why? The first option, in addition to deleting the Record, does all of the processing required when updating properties and fields. This is unnecessary when you are deleting and is likely to cause errors. The second option avoids all the unnecessary pipeline and simply deletes the Record making it much more robust.
Loading the .Net SDK
If you relied on us loading HP.HPTRIM.SDK.dll into the GAC you may have noticed that the 93 installer no longer does this, which has eliminated a number of messy deployment issues for us. For more information see the Microsoft guidelines re GAC installation.
The good news
A key benefit of GAC installation was the ability to write version agnostic SDK applications, the good news is that you can still do this. All you need to do is intercept the assembly loading pipeline and you can specify that HP.HPTRIM.SDK.dll gets loaded from the location specified in the registry.
DataPort Custom Formatter
By default DataPort imports a tab delimited file. What is less well known is that you can write your own formatter to import an arbitrary file format. In this sample I import a very simple XML file. Even though the XML file itself is simple it is a little more complex to import than a tab delimited file due to the fact that it does not follow the essentially columnar layout of a tab delinted file.
We also have the code for our standard tab delimited formatter in the GitHub repo.
BTW, you may hear in the background the happy sounds of my 2 year old son playing in the backyard, enjoy!
Make it faster!
Recently I spent several hours chasing 280 milliseconds that I was convinced should not have been there. The ServiceAPI URL I was calling looked like this:
http://localhost/ServiceAPI/Record?q=container:9000002536&format=json&excludecount=false&pagesize=100&properties=recordtitle,recordowner,recordcontainer,recorddateregistered,recordassignee,recorddatecreated,recordcreator,recordnumber
After hours tuning the ServiceAPI code I managed to tweak a 20 millisecond improvement, still I was sure there was more. I looked once more at the URL and made one small improvement, to this:
http://localhost/ServiceAPI/Record?q=container:9000002536&format=json&excludecount=false&pagesize=100&properties=recordtitle,recordownerlocation,recordcontainer,recorddateregistered,recordassignee,recorddatecreated,recordcreator,recordnumber
Suddenly I had eliminated my extra 260 milliseconds!
Can you spot the difference? Previously I had an incorrect property name, RecordOwner should have been RecordOwnerLocation. Why is this a problem? If the ServiceAPI does not recognise a property it checks to see if it is a valid 'additional field' for this Record. Multiply the time to check this by 100 (the page size) and there is the 260 milliseconds.
The moral
260 milliseconds is not a lot but in a web service environment it adds up so be careful to to include invalid property names in the properties parameter.
Now I am off to see if the 'additional field' code can be optimised.
Generate Outlook linked folders
Background
Prior to CM 9.0 the Outlook integration had a feature to export/import linked folders, at that time linked folders were stored in the Windows registry so the import/export was required any time a user received a new machine. This feature was also of use to those who wished to share their linked folders with their friends.
In 9.0 the architecture of the Outlook integration changed so that linked folders were stored in Checkin Style objects (in Content Manager) not in the registry, which seemed to make the import/export unnecessary, except for that sharing with friends usage.
A partial solution
A partial solution to sharing linked folders is to create Checkin Styles which have a group as the owner, this means that every member of that group will see that Checkin Style. The gap is that a linked folder will not be auto-created for that Checkin Style.
Some sample code
I wrote a sample application that could be the basis for a utility to allow users to create linked folders from Checkin Styles that have been created for them. This might useful in the case where the user has multiple Checkin Styles and does not wish to go through one by one to create a new linked folder for each one. Below is a screen shot from this sample application.
Use Powershell to import a folder of files
There are a variety of ways to import files to Content Manager. If you want granular control you may choose to write some code. This Powershell script uses a CheckinStyle to import all EML files from a folder.
Add-Type -Path "c:\[CM Binary Path]\HP.HPTRIM.SDK.dll" $database = New-Object HP.HPTRIM.SDK.Database $database.Id = "L1" $database.WorkgroupServerName = "local" $database.Connect()
Stream a document in the .Net SDK
The standard methods of getting a document from in the SDK (e.g. Record.GetDocument()) fetch the entire document from the document store before giving you access to the document. DownloadNotifier allows you to:
I just updated the SDK docs to include some information on using it, also, here is a video if you want to watch me use it.
Database connection caching
Previously if you wanted to avoid the overhead of Database.Connect() in a web service application you had to write some sort of connection pool. In 9.2 this is no longer required as the SDK itself caches connections. The requirements are that:
Sample Code
private static Database getDatabase(string user = null) { Database database = new Database(); database.WorkgroupServerName = "local"; database.Id = "L1"; if (user != null) { database.TrustedUser = user; } _watch.Reset(); _watch.Start(); database.Connect(); _watch.Stop();
Event Filtering in 92
Event processor add-ins are great for responding to events on the workgroup server but and now we can optimise them to only respond to the events (and object types) that we are interested in. Use the 'Configure Events' button in the 'Custom Processes' tab in Enterprise Studio, or watch this video for more information. For more details on the event processor addins see my previous post and the sample.
CM 9.2
Microfocus Content Manager 9.2 has been released and the updated SDK documentation, along with SDK release notes is available.
String Search parsing and filtering
Sometimes I overlook some pretty important things. I realised recently that I had missed an interesting behaviour of the TrimMainObjectSearch string searching.
A valid search with filtering
The search below will search for all records where the Additional Field 'Alcohol Level' is greater than zero and filter on Record Type == "Infringement".
TrimMainObjectSearch recordSearch = new TrimMainObjectSearch(db, BaseObjectTypes.Record); recordSearch.SetSearchString("AlcoholLevel>0"); recordSearch.SetFilterString("recType:Infringement");
Be careful with search grammar items in string searches
I was caught out the other day using hard-coded string searches in a ServiceAPI application. It can be convenient (or necessary) to store canned searches (e.g. 'extension:docx OR extension:doc') at times. The thing to remember is that search strings can be localised and your end user may not be using English.
First, the clauses
Typically the English versions of the search clauses should work irrespective of the user's language, so even if the end user has selected French ' extension' should still be OK. Due to my over cautious nature I still use the internal name (e.g. 'recExtension) which can be found in the list of search clauses in the ServiceAPI.
Then the grammar items
Search grammar items (such as 'or' and 'and') are not language neutral so 'recExtension:doc or recExtensiom:docx' will not produce any results if your end user has selected Dutch as their language, what you will need is 'recExtension:doc of recExtensiom:docx'.
SDK
Getting the caption for a search grammar item in the SDK is simple:
EnumItem item = new EnumItem(AllEnumerations.SearchGrammarItem, (int)SearchGrammarItem.And).Caption
ServiceAPI - .Net
It is nearly as simple to get the grammar items using the ServiceAPI .Net client.
TrimClient client = new TrimClient("http://localhost/ServiceAPI"); client.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials;
Warning when setting the ACL via the SDK
Overview
In the native (or web) client if I attempt to set an invalid ACL on a Record I will get a warning, something like this:
ErrorMessage in the Visual Studio Debugger
The way we optimise for performance in the SDK has a potentially confusing side effect in the Visual Studio debugger. In the following video I demonstrate how inspecting a Record (or any other TrimMainObject such as Location, Classification etc) caused ErrorMessage and Error to be re-set.
DataPort's Custom DataFormatters - Sample
Many people know that, out-of-the-box, DataPort supports importing data from tab delimited text files. Many people don't know that users can create custom formatters to allow data from any source to be imported. Maybe 3 people know that our tab delimited formatter is actually a custom data formatter baked into DataPort.
Below I have posted the code for a sample custom data formatter. It is loosely based on our tab delimited formatter but will hopefully provide some good insight into how to develop one for an alternate type data source.
References
A custom data formatter requires that the following references are made:
The Code
using System; using System.Collections.Generic; using System.IO; using System.Windows.Forms; using HP.HPTRIM.DataPort; using HP.HPTRIM.DataPort.Framework.DataFormatters;
Use 'local' when running on the Workgroup Server
One tip that is not as well known as it should be is the use of the reserved work 'local' to refer to a local Workgroup Server. Any time a .Net SDK application is talking to a Workgroup Server on the same machine you should use 'local' for the Workgroup Server name, rather than using the machine name. Why is this? Because 'local' will use a named pipes connection to the Workgroup Server rather than going through the network layer.
Web Client / ServiceAPI Users read this!
Of course the Web Client, ServiceaAPI and WebDrawer are .Net SDK applications, so of you are connecting to a WorkGroup Server on the same machine use 'local'. Note the MSI will probably not suggest this to you so you wil have to edit the hptrim.config manually. Make it look something like this:
<workgroupServer port="1137" workPath="C:\HP Records Manager\ServiceAPIWorkpath" name="local" alternateName="MY_ALTERNATE_WGS" alternatePort="1137" />
Hello, World!
.Net SDK Developers
If you are writing an application to run on the same machine as the Workgroup Server do something like this:
using (Database database = new Database()) { database.Id = "J1"; database.WorkgroupServerName = "local"; database.Connect();
Web Service performance tips
Whether you are using the ServiceAPI or your own web service built on the .Net SDK there a re a couple of things you can do to improve performance.
Object Cache
The object cache allows you to cache Records (and other objects) in memory, this removes the requirements to connect to the database next time this object is requested. This, is of course, most useful when certain Records are requested more than once. You can increase this to up to 99,999 for server applications (see below).
SDK Help
To celebrate the announcement of HPE Content Manager I am having my own launch of the first phase of the new, unnoficial, online 83 .Net SDK help site.
It is made up of:
Depending on the interest levels there will be more articles to come.
Powershell to extract renditions
There may be a way to extract all PDF renditions for a selection of Records via the native client but I do not know how to do it. When I have powershell I don't worry about the client, I just write a script, like this one...
Add-Type -Path "c:\trunk\AnyCPU\Debug\HP.HPTRIM.SDK.dll" $database = New-Object HP.HPTRIM.SDK.Database $database.Id = "I1" $database.WorkgroupServerName = "local" $database.Connect()
Record.EditDocument
I spend as little time with the COM SDK as possible but apparently it had the method Record.EditDocument which would check a document out to Offline Records and open it for editing. This no longer exists in the .Net SDK but you can still achieve the same end. Here is a simple sample.
using (Database database = new Database()) { database.WorkgroupServerName = "local"; database.Id = "I1"; database.Connect();
SDK UI
Overview
Some time ago there was a rationalisation of the UI components offered in the RM .Net SDK, the result of which is that some options that were available in the COM SDK are no longer to be found. For example the ability to view an electronic document via Record.ViewUI no longer exists.
What can you do?
As of RM 8.1 the following UI classes exist in the .Net SDK:
Some Code
The following extracts come form this very simple sample project.
Edit Record Properties
Record record = new Record(_database, 9000000000); if (HP.HPTRIM.SDK.PropertyEditor.EditModal(GetDesktopWindow(), record)) { record.Save(); }
Select Records
TrimMainObjectSearch search = new TrimMainObjectSearch(_database, BaseObjectTypes.Record); search.SelectAll();
Web and native client add-in
Background
Many customers and partners are familiar with extending ITrimAddIn in the RM SDK to create a native client add-in and in 83 record addon was added to the web client. This sample looks at one approach to maintaining paralell addons in the native and web client.
The video
The code
Here is the code:
The current thread is attempting use a database object that is currently being used by another thread...
A sad story (with a happy ending)
A short post, on this potentially confusing error. I remember some time ago spending much too long debugging an application with this error. I knew that the Database was not thread safe and I was sure that I was explicitly disposing every Database I instantiated. It was a fairly large application though so I kept second guessing myself. Also this was in 8.1, before we had removed unnecessary use of IDisposable so it was more difficult than it should have been to run code analysis to work out what I was not disposing. Finally, I tracked it down, it was not a Database object at all, at least not directly, it was a TrimMainObjectSearch! Someone (trust me, not I) had used a TrimMainObjectSearch without disposing it. At some later time the GC came along and attempted to clean it up, the TrimMainObjectSearch contains a reference to the Database and the GC was on a different thread, hence the error.
The moral of the story!
In versions of RM prior to 82 always explicitly dispose both your Database and TrimMainObjectSearch objects.
ADFS from a .Net SDK application
Overview
In a previous post I examined how to configure RM to authenticate via ADFS when using the native client, in this post I show how to write a .Net SDK application to authenticate via ADFS.
The code
This is the console application I used in the video above
class Program {
Lookupset Items in the .Net SDK
Overview
In RM 82 Lookup Set Items were freed from the restrictions of being a child object type. This means that a lookup set item is now a main object, at the same level as the lookup set itself. Why was this done? Mainly to allow for large lookup sets. The parent child relationship among RM objects has practical limits on the number of children a main object can have.
Adding a new lookup item
The new way to add a new item is to instantiate it using the lookup set as the constructor parameter, for example:
LookupSet lookupSet = new LookupSet(database, 9000000000); LookupItem lookupItem = new LookupItem(lookupSet);
Instantiating a record using the .Net SDK
Overview
Once you know its URI (or Record Number) there are a few ways to instantiate a record in the .Net SDK, choose the one that best suites your needs. Each main object type (e.g. Location, History, Hold etc) follows the same pattern seen below.
Constructor
Use the constructor with either the record URI or number. This method will throw a TrimException if the record is not found.
By URI
try { Record record = new Record(database, 900000000); Console.WriteLine(record.Title); } catch (TrimException ex) { Console.WriteLine(ex.Message); }
By record number
try { Record record = new Record(database, "REC_1"); Console.WriteLine(record.Title); } catch (TrimException ex) { Console.WriteLine(ex.Message); }
Database 'find by' method
This will not throw an exception but the resulting object will be null if the Record is not found.
By URI
Record record = database.FindTrimObjectByUri(BaseObjectTypes.Record, 90000000) as Record; if (record != null) { Console.WriteLine(record.Title); }
By record number
Record record = database.FindTrimObjectByName(BaseObjectTypes.Record, "REC_1") as Record; if (record != null) { Console.WriteLine(record.Title); }
By URN
Find by URN can be useful for those occasions when you want to persist a unique identifier for a record (or other object type).
TrimMainObject trimMainObject = database.FindTrimObjectByURN("trim:H1/rec/9000000000"); if (trimMainObject != null) { Console.WriteLine(trimMainObject.Name); }
Database.TrustedUser and web applications
Background
When building a client side SDK application authentication is rarely anissue, you simply connect your database and allow it to connect using the user's credentials. Server side applications can be a little more complex and require some form of impersonation.
.Net impersonation
In the past when we have built web application or web services we have sometimes relied on .Net impersonation. This allows us to impersonate the credentials used by the browser (assuming authentication is enabled in IIS but has a few issues, including:
Database.ConnectAs()
The Database object has a method called ConnectAs(), which due to the fact that you must pass the username and password is of limited value.
Database.SpawnImpersonatedDatabase()
To the best of my knowledge this exists only for backwards compatibility, I do not recommend using it.
Database.TrustedUser
This is where you should be if you are creating a server side application, such as a web site or web service. TrustedUser requires that the credentials used to run the application have permission to impersonate other users, there are two ways for a user account to have this permission, if it is:
For an IIS web application that user that must have this permission is the Identity used by the Application Pool attached to the IIS application.
Once you are running as one of these users then you can impersonate any RM user like this:
using (Database database = new Database()) { database.Id = "H1"; database.WorkgroupServerName = "local"; database.TrustedUser = "PersonA"; database.Connect(); }
SDK Searching by record type
Today's question of the day, searching by record type...
The imperative approach
Here we use the methods built into the TrimMainObject search to build up the search clauses to search for the record type with Uri 1.
using (Database database = new Database()) { database.Id = "H1"; database.WorkgroupServerName = "local"; database.Connect();
A simple COM SDK VB.Net application
In a previous post I demonstrated a simple .Net SDK program, here I will look at a simple COM SDK program.
The code
Here is the code I ended up with in the video.
Sub Main() Dim database As New TRIMSDK.Database Dim record As TRIMSDK.Record = Nothing Dim container As TRIMSDK.Record = Nothing
Security caveats for a record
Question of the day - Find all security caveats for a record.
HPRM has an expansive SDK and often when I am asked a question I have to do some elementary research to find the answer, that is one excuse although maybe I am just getting forgetful. So I don't forget the process as well as the answer today in addition to answering the question I will step through my thought process.
The thought process
If I do not immediately know the answer I go through a process a little like this:
The code
This will display a comma separated string containing the security level and names of all caveats.
Record rec = new Record(database, 9000000221); Console.WriteLine(rec.Security);
We could take this list and find all caveats with the corresponding names.
Record rec = new Record(database, 9000000221); foreach (string caveatName in rec.Security.Split(new string[] { ", " }, StringSplitOptions.RemoveEmptyEntries)) { var caveat = database.FindTrimObjectByName(BaseObjectTypes.SecurityCaveat, caveatName) as SecurityCaveat; if (caveat != null) { Console.WriteLine(caveat.Name); } }
Or better still (depending on how many caveats there are) we could loop through all caveats and find the ones we want.
Record rec = new Record(database, 9000000221); TrimMainObjectSearch caveatSearch = new TrimMainObjectSearch(database, BaseObjectTypes.SecurityCaveat); caveatSearch.SelectAll(); foreach (SecurityCaveat caveat in caveatSearch) { if (rec.SecurityProfile.IsCaveatOn(caveat)) { Console.WriteLine(caveat.Name); } }
Offline Records
Spending most of my life in the server side world of web services I rarely pay much attention to offline records in HPRM, other people obviously do given that twice in the past week I have been asked questions about searching for them.
Firstly, it will not work from a web service, offline records require access to the user's local machine.
Now, how do I search from the SDK? Just as I would search for any other object type it turns out.
TrimMainObjectSearch search = new TrimMainObjectSearch(database, BaseObjectTypes.OfflineRecord); search.SetSearchString("status:Draft");
Delete all Records
The setup
Had a support call escalated to me a few days ago, customer wanted a method to delete all records. Sounds interesting I thought, maybe a bit of an edge case, so I suggested they do this:
TrimMainObjectSearch search = new TrimMainObjectSearch(database, BaseObjectTypes.Record); search.SelectAll();
Thumbnails
Intro
This post pulls together techniques discussed in previous posts to examine how to auto generate thumbnails for images, store them as rendtitions, expose them via the ServiceAPI and display them in WebDrawer.
Warning: ServiceAPI plugin feature featured in this post requires HPRM 8.1.1 or later.
The talking
Links
Code available here:
Related Posts
Some related posts to get started:
A first event processor add-in
Event processor add-in?
The HPRM workgroup server processes a variety of scheduled events, such as word indexing and notifications. It is possible for you to create your own custom add-ins to be triggered by one of the many record (or other object) events. I have written an intro to HPRM SDK programming in a previous post.
Getting started
This is a video of me creating a simple event processor add-in.
Basic debugging tips
It can be difficult to work out why a program like an event processor add-in is not working. These are some of the strategies I use:
If you have any other tips please put them in the comments below.
Thumbnail Views (a response for Brian)
A comment
Someone called Brian posted a comment on one of my posts a month ago, unfortunately I never noticed this comment. I find the best place for actual conversations to be the various HP RM Discussion boards, I monitor them daily but rarely look at comments on this blog. Just in case Brian is reading I will answer his question here.
The question
Do you think it would be possible, and effective, to leverage the preview capability to display a thumbnail grid of search results?
My answer
I have thought about this on a number of occasions and my conclusion is no. The preview is slow and does not have a facility to generate a preview of a single page. I do not see it as a solution for a thumbnails view.
Another answer
If what you are interested in is thumbnails of images (PNG, GIF, JPG, etc but not other document types) and you are happy to do your search in WebDrawer (not the Web Client) then there is a potential solution. I have previously supplied a sample EventProcessor add-in to auto create a thumbnail rendition of each image document as it is created/modified. It is quite simple then to have a custom search view in WebDrawer to display this thumbnail rendition.
So...
I will come back and check this post in a week or so, if you really want the samples I describe above add a comment. Or put in a request on one of the discussion boards. I cannot make any promises as compiling samples has to fit in around other work but I will do my best.
TrimMainObjectSearch FastCount
The Problem(s)
In the .Net SDK TrimMainObjectSearch class we have a property called FastCount. One problem is that it is not that fast, it is faster than reading the entire result set and counting manually but, especially on a large dataset, it can still be slow. The other problem is that it can be optimistic, yes it is possible that it will say there are more results than you can actually see.
What is going on?
It is possible for a Record to have one or more ACL's set to 'Same as Container'. Of course a container may also have one more more ACL's set to 'Same as Container'. This reference style ACL is particularly complex to interrogate and we only support it to 2 levels at the database level. Anything beyond two levels is managed by the security filtering code in the client/SDK.
So, if you have Records set to 'Same as Container' and the container is also set to 'Same as Container' and they match your search criteria then FastCount will include these. This is because FastCount is calculated at the database level.
The moral
FastCount is interesting as a source of information but should not be relied upon as an exact count any more than my Google search which tells me there are (approx) 41, 300,000 results for the search 'apple pie'.
HPRM 82
HPRM 82 has a partial fix to the optimism of FastCount. In the System options (under the Security tab) there is the option 'When counting search results, ensure full security filtering is applied when displaying totals'. This causes the client / SDK to filter the search results to adjust FastCount. The upside is that FastCount is now accurate, the downside is that it is slower.
The future
Hopefully the future holds a FastCount capable of dealing with deep reference style security at the database level, making it both fast(ish) and accurate.
Disposal in HPRM 82
Good and bad news
In an earlier post I discussed when and what to Dispose when using the .Net SDK. The good news in HPRM 82 is that disposal is much simplified. The bad news is that it is a breaking change from previous versions.
What to dispose
There are now only two objects that require disposal, they are Database and DocumentStream.
What not to dispose
Previously it was required that you dispose TrimMainObjectSearch and most other objects could be disposed, even though this had no actual effect. In HPRM 82 the 'IDisposable' interface has been removed from all objects other than those that require disposal (i.e. Database and DocumentStream). This will require you to edit and re-compile any .Net SDK that calls Dispose unnecessarily.
Why?
Why did we cause you this pain you may ask? The primary reason was to assist both our and your development teams to write quality software. In the previous model there were many classes that implemented IDisposable but only a few that really needed disposal, this made it easy to feel like you had done the right thing when in fact you had overlooked disposal on the one object that really required it. One example of this problem is our use of static code analysis tools such as Coverity to help us find problems in our code. The vast number of unnecessary Dispose implementations leads to a vast number of false positive errors and a decreased likelihood of us spotting the actual errors.
My first HPRM SDK application
Introduction
If you are not a programmer but want to do some simple HPRM SDK programming here is a start. It is actually quite easy to achieve some fairly powerful outcomes, like anything the curve gets steeper as your ambition grows.
The Video
Please forgive my constant sniffing in this video, I have been battling a cold for a week.
The Code
using (Database database = new Database()) { database.Id = "F1"; database.Connect();
Avoiding memory leaks when using the .Net SDK
Update
For an update on disposal in HPRM 8.2 see my latest post.
Update for 9.0
It used to be the case that code that accessed the Database object of a TrimMainObject (as seen below) would be problematic. This is no longer the case. The only instance of the Database object that requires Disposal is the on you instantiate and connect directly.
Console.WriteLine(myRecord.Database.CurrentUser.SortName);
Intro
Just when I start feeling complacent and good about myself as a careful/professional programmer is when I am likely to make a silly mistake or overlook some seemingly innocent code in a code review. Working in a .Net language (typically C#) I am much safer than those who spend their lives in C++ yet using a library that contains unmanaged code (in this case the HPRM .Net SDK) provides the ideal opportunity to embarrass myself.
The HPRM .Net SDK implements the Dispose interface on almost every object in the SDK. For example the follow code will compile:
Location location = new Location(database, "a person"); location.Dispose();
This is a redundant artefact of an early version of the SDK, most of these Dispose methods do nothing and need not be called. Not having to Dispose every single object makes .Net SDK much simpler yet there are important exceptions to this 'never dispose' rule.
What must I Dispose?
There are three objects that must always be disposed to avoid memory leaks, these are:
Disposal patterns
The two best ways to Dispose are using either the 'using' statement or the try/finally pattern. Both of these ensure that your object will be disposed even if execution terminates abnormally, for example as the result of an error.
Using
The using statement sets a scope for the object and ensures it is disposed before going out of scope. The Database can be instantiated in this was as seen in the below example.
using (Database database = new Database()) { database.Id = "G1"; database.Connect();