Micro Focus Content Manager SDK 9.3
Programming in Content Manager

Introduction

The main objects

The .NET SDK has a few of abstract classes from which the rest of its objects descend. They are:

Most objects in the .NET SDK have their own constructors and can be created directly. If while programming an API based application you are unsure about how to perform a specific Content Manager function it is often helpful to see how the function is performed in the Content Manager Client.

Using the Trim Application object

This object has methods and properties which describe and adjust the environment which your application is running in. Two important methods are:

  • Initialize() – As previously mentioned.
  • RuntimeEnvironment = Environments This tells Content Manager what the runtime environment of this application is. Content Manager will then make changes to its behavior necessary for that environment.

Using the Database object

The Database object is one of the main objects managing other Content Manager API objects. The Database object is responsible for managing the session connection to a Content Manager Workgroup Server.

The Database object is a Creatable object, meaning that you can use the ‘New’ keyword to construct a new instance of it. Before the actual connection is created you might specify the ID of the database to use and the location of the Workgroup Server’s name or URL address to use.

Code Example

using (Database objDB = new Database())
objDB.Id = "PD"; //every Content Manager Database has a two character id
objDB.WorkgroupServerName = "local";
objDB.Connect(); // throws an exception if the connection fails
}

If the ID and WorkgroupServerName (or URL address) property of the Database are not specified a connection will be made to the users default database when required. If the user has not specified a default database in this situation an exception will be thrown.

Creating and modifying Records

Creating new Records

Before creating a new Record you need to load a RecordType object which specifies the type of Record you are creating. After creating a record or changing an existing record you need to save it.

Code example

RecordType objRecType = new RecordType(objDB, "Document");
Record objRec = new Record(objDB, objRecType);
// You will need to modify the records properties
objRec.Save();

Accessing existing records

To read information stored on records in an Content Manager database, the API programmer must first determine how to access the records required. If a particular record’s internal or external unique identifier is known, the associated record can be accessed directly and efficiently using the Record’s constructor. If neither of these unique identifiers is known, it will be necessary to construct a search.

Getting a record by record number

Every Record in Content Manager has a unique record number. This follows a pattern defined by the record type and can be manually entered by the user or set to be automatically generated by Content Manager. Although the commonly used term is ‘number’, it is more correctly an identifier, as it is a string that may contain alphanumeric characters. This string is accessible through the Record object’s Number property.

The record number can be used as the argument to be passed to the Record object’s constructor, which takes the current database and a variant for the unique identifier and returns a pointer to the instantiated record.

Code Example

// This statement instantiates Record 2015/0059 by expanded record number
Record objRecord = new Record(objDB, "2015/0059

Note: Content Manager stores the record number in two formats, expanded (e.g. "2015/0059") and compressed (e.g. "15/59"). Either can be substituted above.

Getting a Record by URI

The Unique Row Identifier or URI of a record is an internal unique number that is transparent to the everyday user of Content Manager. It is the primary key on the TSRECORD table in the database and provides an internal unique identifier for every record. The URI can be used as the argument to be passed to the Record object’s constructor, similarly to the previous example.

Code Example

// This statement instantiates a record by its URI
Record objRecord = new Record(objDB, 130);

Once a record object has been created, the programmer can access properties and call methods on the object. These are discussed in the following subsections.

Reading record data

Basic properties

Most of the metadata directly associated with a record is exposed through properties on the Record object. Most properties return primitive data types (strings, numbers or dates) and can be interrogated directly. The meanings of these properties are generally self-evident from their names, but are also given in the object browser.

Examples of basic readable properties of a record are:

Property Example Value
Number "G1997/0770"
Title "Greenhouse Journal of Global Warming - Dugong Habitats"
DateCreated 8/20/1997
ExternalId "GJGW 97PB"
AccessionNumber 5617

Code Example

Record objRec = new Record(objDB, "G97/770");
if ((objRec.AccessionNumber > 5000) &&
(objRec.DateCreated < TrimDateTime.Parse("01/01/2000")))
{
MessageBox.Show (objRec.Title, "Record " + objRec.Number.ToString());
}

Accessing related objects

Many attributes of a Content Manager Record represent other objects, such as the RecordType, Classification and Container attributes. These are properties where the data type of the property is an object.

Code Example

Record objRec = new Record("G97/770");
Record objCont = objRec.Container;
MessageBox.Show(objCont.Number.ToString());

Accessing record Location information

A Content Manager record has various properties concerning related location information. These properties of a Record all return an instantiated Location object:

  • HomeLocation – normal Location of the Record
  • OwnerLocation – Location of the owner or responsible unit for the record
  • Author – person who authored the electronic document
  • Creator – person who registered the record in Content Manager
  • Addressee– person to whom the record is addressed
  • PrimaryContact – the main contact person (or organization) for the record

Code Example

Record objRec = new Record("G97/770"); // get the record
Location objLoc = objRec.Author; // get the author location object
MessageBox.Show ("Author’s name is: " + objLoc.FullFormattedName);

Updating records

So far we have only considered the methods for reading information from records in Content Manager. The API also allows you to update Content Manager records, either by updating the values of properties on a given record object, or by calling methods on the record. Updating properties is the simplest way to modify the metadata of a record. You simply assign a new value of the correct data type to the named property of the object. Field-level verification is carried out, and an error will be raised if the property update is invalid (see also the section on ‘Verifying’ below). For more complicated types of update to a record, you must generally call methods that instruct Content Manager to modify the record, based on arguments passed.

Modifying properties

The simplest way to update data in a Content Manager record is to modify the named properties on the Record object. This can only be done on properties that are not marked as read-only. This includes most of the Date properties, certain Location properties (Author, Addressee and OtherContact) and miscellaneous properties such as ExternalReference, Priority, AccessionNumber and ForeignBarcode.

Code Example

Record objRec = (Record)objDB.FindTrimObjectByName(BaseObjectTypes.Record, "D15/2");
If (objRec != null)
{
objRec.TypedTitle = "New title for this record";
objRec.DateDue = DateTime.Today.AddDays(7); //Due in 7 days
objRec.DatePublished = DateTime.Today;
objRec.Author = objDB.CurrentUser;
objRec.Save();
)

Calling update methods

To update other data on a record where read-write properties are not available, you must call a method instead. Update methods often begin with the prefix ‘Set…’ and they include a parameter for the new data value you wish to apply.

Code Example

Record objRec = new Record(objDB, "G97/770"); // get the record
Location objCreator = new Location(objDB, "Peter Abbott"); // get the location
objRec.SetCreatorLocation(objCreator); // set the creator
objRec.Save();

In many cases other parameters can be specified that control the behavior of the update:

Code Example

Record objRec = new Record(objDB, "G97/770"); // get the record
objRec.SetAssignee(objDB.CurrentUser(), null, DateTime.Today.AddDays(-1)); // set
objRec.Save();

Updating properties using SetProperty

To update a record’s properties where the internal identifier of the property is known, you can use the SetProperty method. This requires passing the property identifier and a variant containing the data value.

Code Example

Record objRec = new Record(objDB, "G97/770"); // get the record
objRec.SetProperty(PropertyIds.RecordTitle, "New Title"); // Set the title
objRec.Save();

User Defined Fields

Content Manager allows user-defined fields to be assigned to records. These user-defined fields cannot be interrogated using normal named properties of the record object. Instead, accessing user defined fields is carried out using a dedicated object for managing these fields

  • the FieldDefinition object. Each base object has a pair of methods for manipulating user-defined fields, GetFieldValue and SetFieldValue. Each method takes a populated FieldDefinition object as a parameter.

Code Example (GetFieldValue)

Record objRec = new Record(objDB, "G97/770");
FieldDefinition fdDocType = new FieldDefinition(objDB, "Document Type*");
UserFieldValue ufvDocType = objRec.GetFieldValue(fdDocType);
MessageBox.Show(ufvDocType.ToString());

Code Example (SetFieldValue)

Record objRec = new Record(objDB, "G97/770");
FieldDefinitionList udfs = objRec.RecordType.UserFields;
UserFieldValue ufvCurrent = null;
foreach (FieldDefinition udf in udfs)
{
switch (udf.Format)
{
case UserFieldFormats.Datetime:
TrimDateTime tdta = new TrimDateTime("2015-01-01");
ufvCurrent = new UserFieldValue(tdta);
break;
case UserFieldFormats.String:
case UserFieldFormats.Text:
case UserFieldFormats.Xml:
ufvCurrent = new UserFieldValue("Freddy");
break;
}
if (ufvCurrent != null)
{
objRec.SetFieldValue(udf, ufvCurrent);
}
}
objRec.Save();

Verifying and error trapping

When a record object is modified via the API, there are two levels of verification that must be carried out before the changes can be committed to the database. The first is field-level verification, which checks that the change to an individual property is legal. An example would be that the Date Registered is not in the future. If a property update cannot be carried out because of field-level verification, the method call or property assignment will cause a run-time error to be raised and the update will not be carried out. The second level of validation is object-level verification (sometimes called cross-field verification). This checks that the values of all fields on the object are consistent with each other. An example of object-level verification would be that the Date Registered is not earlier than the Date Created. Objectlevel verification is performed when the object’s Verify or Save methods are called.

The Verify method

Every base object has a ‘Verify’ method. This can be called to perform object-level verification prior to saving the object. The method returns false if there are any errors in the state of the object, and the error description will be stored in the object’s ErrorMessage property. If there are no errors, the method returns true and the Verify property (see below) is set to true. The method contains an optional parameter "failOnWarning" which, if set to true, will cause the Verify method to check for warning conditions as well as error conditions, and to fail if a warning is encountered.

Code Example

Record objRec = new Record(objDB, "G97/770");
if (objRec.Verify(true))
{
MessageBox.Show (objRec.ErrorMessage, "Verify Failed");
}
else
{
objRec.Save();
}

If it is not called explicitly in code, the Verify method will be automatically called before an object is saved (see below) and if verification fails it will not be saved. This ensures that data cannot become corrupted and that business rules are observed when using the API, just as they are for users of the Content Manager Client interface.

The Verified property

Every base object also has a ‘Verified’ Boolean read-only property, which is false whenever the object is instantiated. It is set to true when the Verify method confirms that it is in a legal state to be saved to the database (regardless of warning conditions).

Trapping run-time errors

It is up to the programmer to determine how they wish to deal with possible errors when updating an object. However, they must be aware that error checking takes place even when directly updating properties, so it will be necessary to provide some error-trapping code to prevent run-time errors being displayed to the user if there is a possibility of errors being raised.

Saving the record to the database

All of the update methods and property changes made through the Record interface are only applied to the object in memory. The changes are not committed on the Content Manager database until the object is saved. Calling the Save method on the record object will commit the changes to the database, applying all updates since the object was instantiated (or since it was last saved). Note that if the record has not been verified, Save will automatically call the Verify method and will only commit the changes if the verification succeeds. Various code examples using this method can be found throughout this document.

New records and electronic documents

Creating a container file

This scenario describes the general processes for using the SDK to create a record of a generic record type we are calling a ‘Container File’. In this and the next scenario (Creating a Document) we are assuming that the reader is familiar with the concept of record types. While it is up to the administrator of each Content Manager implementation to determine the record types to be used, it is typical to follow a standard records management practice of having at least two record types, one representing container files (or folders) and one representing documents (the actual names used for the record types may of course vary). Container files are usually created and maintained by specialist records managers, as it is generally at this level that classification systems, retention schedules, security, keywords, controlled titling and other records management metadata are applied. Documents, on the other hand, are usually created by end-users, and require little specific metadata other than the identification of the appropriate container file to which the document belongs, as all other metadata and context is inherited from the Container. The general steps for creating a new container file record are as follows:

  1. Instantiate the appropriate Record Type object
  2. Instantiate a new record object of this type
  3. Identify the Classification or keywords for titling the record (optional)
  4. Set the free text title
  5. Assign security levels and caveats (optional)
  6. Relate the record to associated Locations (optional)
  7. Relate to other records (optional)
  8. Assign other metadata or user-defined fields (optional)
  9. Assign a record identifier
  10. Save the Record object

Creating a record of a given type

When creating any record, the Record Type for the new record must be identified. This is passed to the record object’s constructor along with the database which the new record is to belong to. See the Creating new records example.

Note: It is possible to create new Record Types using the SDK; however, this is not recommended as this is generally an administrator’s function only.

Controlled and free text titling

Titles for container files are often subject to controlled vocabulary or classification structures such as a thesaurus or file plan, which give records managers’ greater control over file creation, retrieval and retention. Even when such controlled titling is used, each file will typically also have a ‘free text’ title part. The titling method used is determined by the record type, and is usually set by the Records Manager administrator. Thus a record with Classification titling may have a title such as: "Insurance – Property – Storm damage to Mackay information center", where the first two terms are generated from a predefined hierarchical classification structure and the remaining part of the title is ‘free text’ describing the specifics of the file. The generated title terms are determined by the lassification codes, usually defined as a numerical sequence such as "610/600/". The free text title is set via the TypedTitle property.

Code Example

RecordType objRecType = new RecordType(objDB, "TestingClass");
Record objRec = new Record(objDB, objRecType);
if (objRecType.TitlingMethod == TitlingMethods.Classification)
{
// Assign classification of 610/600/ = Insurance - Property
objRec.Classification = new Classification(objDB, "610/600/");
objRec.TypedTitle = "Storm damage to Mackay information center";
MessageBox.Show(objRec.GeneratedTitle); // "Insurance - Property"
}
objRec.Save();

Similarly, Thesaurus or Keyword titling allows a file to be titled using either a choice of individual keywords from a controlled list or a specific ‘branch’ of related terms according to a hierarchical structure (similar to a record plan or classification).

Security Levels and Caveats

The security profile of an individual Content Manager record is governed by three security controls: a Security Level, a set of zero or more Caveats, and Access Control. Security Levels and Caveats determine the access that a Content Manager user has to the metadata of a record. These security specifications are usually applied to Record Types (and inherited by records of each type when they are created) but can be set explicitly on individual records. Every user has a maximum security level and zero or more caveats – in order to access a particular record, the user must have the same or a higher security level and must have all the caveats associated with the record.

There are two ways to assign Security Levels and Caveats to a record via the SDK. If the value and number of Levels and Caveats are fixed then this method can be used.

Code Example

RecordType objRecType = new RecordType(objDB, "Document");
Record objRec = new Record(objDB, objRecType);
objRec.Security = "Confidential, Caveat1, Caveat2";
objRec.TypedTitle = "Security Profile Example");
objRec.Save();

The more preferred way (although more verbose) is to retrieve the level and caveats, add them to a security profile and assigning the profile to the record. Both the SecurityLevel object and the SecurityCaveat objects can be instantiated by full name or by abbreviation. The instantiated SecurityLevel object can then be assigned to a TrimSecurityProfile’s SecurityLevel property. Each instantiated SecurityCaveat object can be added via the SecurityProfile’s AddCaveat method. The SecurityProfile can then be assigned to a record’s SecurityProfile property.

Code Example

RecordType objRecType = new RecordType(objDB, "Document");
Record objRec = new Record(objDB, objRecType);
SecurityLevel secLevel = new SecurityLevel(objDB, "Unclassified");
SecurityCaveat secCaveat1 = new SecurityCaveat(objDB, "Caveat1");
SecurityCaveat secCaveat2 = new SecurityCaveat(objDB, "Caveat2");
TrimSecurityProfile secProfile;
secProfile = objRec.SecurityProfile;
secProfile.SecurityLevel = secLevel;
secProfile.AddCaveat(secCaveat1);
secProfile.AddCaveat(secCaveat2);
objRec.SecurityProfile = secProfile;
objRec.TypedTitle = "Security Profile Example");
objRec.Save();

Record Locations

Defining relationships between a container file and location objects (people and places) provides additional and useful context for the record. Unlike record relationships, which can be user-defined, you can only use Content Manager’s predefined standard relationship types for record locations. Record Locations represent actual (in the case of paper and other physical records) or logical (in the case of electronic records) places where a record resides. Every record in Content Manager has a property representing its Home Location (where the record should normally be). There is also a property for Owner Location – the exact meaning of this can vary according to the practices of each Content Manager implementation, but normally represents the person or body that is responsible for the record. The Home and Owner location of a record are typically derived from the default values for each Record Type, but all record location properties can be set on creation of a new record or modified later. The record object has methods for setting or changing the value of these location properties, which allow the option of specifying the date & time of the change of location (the default is the current time).

Code Example

RecordType objRecType = new RecordType(objDB, "Document");
Record objRec = new Record(objDB, objRecType);
Location objLocation = new Location(objDB, "Administration");
objRec.SetHomeLocation (objLocation);
objRec.Save();

Record Contacts

Unlike record Locations which tend to be internal units, Record Contacts are more commonly people or organizations that have a direct association with the record, and may be internal or external to the organization. Using the AttachContact method, Content Manager allows each contact to be specifically identified as an Author, Addressee, Representative or Client. Other contact relationship types must use the generic type of ‘Other’.

Code Example

RecordType objRecType = new RecordType(objDB, "Document");
Record objRec = new Record(objDB, objRecType);
Location objContact2 = new Location(objDB, "Hewlett Packard Enteprise");
objRecord.AttachContact (objDB.CurrentUser, ContactType.Representative, true);
objRecord.AttachContact (objContact2, ContactType.Client, true);
objRec.Save();

Creating a Document

This scenario describes the general processes for using the SDK to create a record of a generic Record Type we are calling a ‘Document’.

While container files are usually created and maintained by specialist records managers, documents, on the other hand, are usually created by end-users, and require little specific metadata other than the identification of the appropriate container file to which the document belongs, as most other metadata and context is inherited from the container. A document record usually consists of an electronic object (the source document, image or other file), a unique identifier (which may be automatically generated by Content Manager), a record title and any other metadata required to profile and index the record, and a pointer to the container file from which the document derives its context.

The general steps for creating a new document record are as follows:

  1. Instantiate the appropriate Record Type object
  2. Instantiate a new record object of this type
  3. Identify the container file for the document
  4. Set the free text title
  5. Attach an electronic file
  6. Assign the record’s Author or other contacts (optional)
  7. Set Access Control to the electronic document (optional)
  8. Assign other metadata or user-defined fields (optional)
  9. Save the record object

Titling and numbering

Titling for documents is generally straightforward – free text titling is the norm, and the title simply needs to succinctly describe the document or record. Record numbers may be assigned explicitly or they may be automatically generated – this is configured on the Record Type properties. If the number is explicitly assigned, the number (in expanded format) must be assigned to the LongNumber property (it must be unique or the record will not be saved).

Code Example

RecordType objRecType = new RecordType(objDB, "Document");
Record objRec = new Record(objDB, objRecType);
objRec.TypedTitle = "Testing Long Numbers";
objRec.LongNumber = "XK/008934";
objRec.Save();

Assigning to a container

Although it is not compulsory, it is most common that an electronic record is logically assigned to a container file that represents the subject matter, case, client file or other contextual grouping relevant to the document.

To assign a record to a container, the existing container record must be instantiated (by Id or URI) and then passed as an argument to the (contained) record object’s SetContainer method. The method includes a parameter for specifying whether the record is also ‘enclosed in’ the container, i.e. that the current location should reflect that it is with the container.

Code Example

RecordType objRecType = new RecordType(objDB, "Document");
Record objRec = new Record(objDB, objRecType);
RecordType objRecType2 = new RecordType(objDB, "Folder");
Record objContainer = new Record(objDB, objRecType2);
objRec.TypedTitle = "Testing Long Numbers";
objRec.SetContainer(objContainer, true);
objRec.Save();

Attaching an electronic document

Document records can represent physical paper documents, but mostly they will include an electronic attachment, whether this is a word-processing document, scanned image or other type of file. To attach an electronic document to a record, the file name and path must be used to instantiate an InputDocument object. This object is then passed as an argument to the record object’s SetDocument method. The method includes parameters for specifying whether this should replace any existing document (or be added as a new revision), whether it should be marked as checked out to the current user, and any comments to be added to the record’s Notes field. The title of the document (minuse the extension) becomes the title of the record by default.

Code Example

RecordType objRecType = new RecordType(objDB, "Document");
Record objRec = new Record(objDB, objRecType);
InputDocument objDoc = new InputDocument();
objDoc.SetAsFile(@"C:\temp\samples.txt");
objRec.SetDocument(objDoc, false, false, "Created via SDK");
objRec.Save();

Document Author

Record Contacts are Content Manager location objects commonly representing people or organizations that have a direct association with the record. The most common type of Contact to be specified for an electronic document is the Author. Although the AttachContact method can be used for this and other contact types, a shortcut is provided through the Author property. For examples how to do this, see the code examples for:

Locations

Working with Locations

The Location object is an encapsulation of all properties and methods associated with Persons, Organizations, Positions and Groups. Locations can be identified by name or by URI, and can be selected on other criteria, such as date of birth, nicknames, or membership of a particular organization, role or group.

Finding a Person by Name

Although the names of non-persons (Units, Positions and Organizations) must be unique, this is not the case for persons (Staff Names & Contacts). However, Content Manager allows you to store a ‘nickname’ for any person, and this can be used as a substitute for a person’s name when searching. To find a particular person by name, you must pass the person’s combined name and title to the Location object’s constructor.

Code Example

Location objLoc = new Location("Abbott, Peter (Mr)");

If the sub-string does not uniquely identify a location (i.e. there are no matches, or there is more than one match) then a null object will be returned.

Creating a new Staff Member

To create a new staff member, you must instantiate a new location by calling the Location’s constructor. You then define the type of the location by assigning a value (in this case Person) to the TypeOfLocation property. You can then set various properties representing the person’s name, contact details such as telephone numbers and addresses, administrative details such as employee id numbers and so on.

If the new person is to be a Content Manager user, then there are login and security details to be provided. You will need to specify the user’s network login id and optionally an expiry date. For the security profile, you are required to either explicitly state the user’s security level (and optionally any Caveats) and a user category, or if role-based security is used you can specify that the user takes the profile of a predefined group or user.

Code Example

Location objRole = new Location(objDB, "Executive");
Location objLoc = new Location(objDB);
objLoc.TypeOfLocation = LocationType.Person;
// Name
objLoc.Surname = "Evans";
objLoc.GivenNames = "David";
objLoc.Initials = "D";
objLoc.Honorific = "Mr";
// Personal & Administrative
objLoc.IsWithin = true; // Internal
objLoc.IdNumber = "793906";
objLoc.ReviewDate = DateTime.Today.AddYears(1);
objLoc.DateOfBirth = DateTime.Parse("11/29/1976");
objLoc.PhoneNumber = "555 123496";
objLoc.MobileNumber = "+44 7939 062736";
objLoc.Notes = "Created via SDK";
// Login Details
objLoc.CanLogin = true;
objLoc.LoginExpires = DateTime.Today.AddYears(3); // Valid for 3 years
objLoc.LogsInAs = "evansd"; // Network ID login
// Security
objLoc.UseProfileOf = objRole;
// Confirm & Save
if (objLoc.Verify(true))
{
objLoc.Save();
MessageBox.Show(objLoc.FormattedName + " created.");
}
else
{
MessageBox.Show(objLoc.ErrorMessage);
}

Relationships such as membership of units or reporting lines are created using the AddRelationship method and passing parameters for the related location and the relationship type.

Code Example

Location objGroup = new Location(objDB, "Board of Directors");
Location objMember = new Location(objDB, "Evans, David (Mr)");
objMember.AddRelationship(objGroup, LocRelationshipType.MemberOf, true);
objMember.Save();

Addresses (including electronic addresses such as email or URL) are added by calling the New method on the location’s ChildAddresses or ChildEAddresses collection properties.

Code Example

Location objPerson = new Location(objDB, "Evans, David (Mr)");
LocationEAddress objEmail = objPerson.ChildEAddresses.New();
objEmail.ElectronicAddressData = "evansd@gmail.com";
objEmail.ElectronicAddressType = EAddressType.Mail;
objPerson.Save();

Searching Content Manager using the .NET SDK

Searching for Content Manager objects

One of the most powerful features of Content Manager is the wide range of search criteria that can be applied to select obects from the database. .NET SDK provides the TrimMainObjectSearch class to provide this functionality. This class allows you to combine a number of search criteria together using boolean logic and then iterate through a resulting set of objects. Note that the TrimMainObjectSearch class can only be used to search for objects of a specified type, for instance, you can create a TrimMainObjectSearch to retrieve records. As its name implies, this class can be used to search for any SDK object that inherits from TrimMainObject.

Code Example

TrimMainObjectSearch records = new TrimMainObjectSearch(db, BaseObjectTypes.Record);
records.SelectAll();

Specifying the search criteria

The TrimMainObjectSearch class provides a number of simple "canned" methods for searching that do not involve any boolean logic. These are simple to use if you have a straightforward query, they include:

  • SelectByPrefix
  • SelectFavorites
  • SelectByUserLabel
  • SelectNone
  • SelectAll
  • SelectByUris
  • SelectTopLevels
  • SelectThoseWithin

To use the string search syntax available in Content Manager (refer to the Content Manager’s Help file for examples). There are three main components to a string search – the primary search string, a set of filters (these work as if they were combined with the primary search string using a boolean ‘and’) and the sort specification. To create a query in this way, you use the SetSearchString method to specify the primary search string, and then you can optionally specify any filters using SetFilterString and specify a sort using AddSortItemAscending/AddSortItemDescending. This approach is useful if you want to provide the user with a simple edit control for specifying a search, and is also very useful for web-based applications.

Code Example

TrimMainObjectSearch objSearch = new TrimMainObjectSearch(objDB, BaseObjectTypes.Record);
objSearch.SetSearchString("createdOn:this year and creator:me");
objSearch.SetFilterString("type:document");
objSearch.SetSortString("createdOn");
MessageBox.Show(objSearch.FastCount.ToString());

Another option is to work with TrimSearchStackItem objects. The primary search criteria is represented internally as an array of TrimSearchStackItem objects, arranged as a reverse-polish stack to indicate operator precedence. A TrimSearchStackItem can be an operator (TrimSearchOperator) or a search clause (TrimSearchClause). You can build up a query using the internal search stack of the TrimMainObjectSearch, by using such methods as AddSearchClause, And, Or and Not. A read of the Wikipedia article on Reverse Polish notation would be most beneficial for developers unfamiliar with this style of expression. Because filters are all automatically "and-ed" together, they are simply represented as an array of TrimSearchClause items. For sorting, there is a TrimSearchSortItem class and a corresponding array.

Code Example

TrimMainObjectSearch objSearch = new TrimMainObjectSearch(objDB, BaseObjectTypes.Record);
TrimSearchClause objClause1 = new TrimSearchClause(objDB, BaseObjectTypes.Record,
SearchClauseIds.RecordCreatedOn);
objClause1.SetCriteriaFromString("this year");
TrimSearchClause objClause2 = new TrimSearchClause(objDB, BaseObjectTypes.Record, SearchClauseIds.RecordCreator);
objClause2.SetCriteriaFromString("me");
objSearch.AddSearchClause(objClause1);
objSearch.AddSearchClause(objClause2);
objSearch.And();
TrimSearchClause objFilter = new TrimSearchClause(objDB, BaseObjectTypes.Record, SearchClauseIds.RecordType);
objFilter.SetCriteriaFromString("document");
objSearch.AddFilterClause(objFilter);
objSearch.AddSortItemAscending(SearchClauseIds.RecordCreatedOn);

These individual classes provide extra features that allow even more elaborate queries to be built.

Retrieving the results of the search

The TrimMainObjectSearch derives from IEnumerable and so you can use a standard "foreach" loop to retrieve items that match the selection. In addition, there are two methods that will return the search result as an array of unique object identifiers (corresponds to the Uri property of a TrimMainObject).

Code Example

...
foreach (Record objRec in objSearch)
{
MessageBox.Show(objRec.Title);
}

Other search features

Purpose filtering

You can specify that the items in the selection are eventually intended for a specific pupose. This may apply additional hidden filtering to the selection, although not always. As you are iterating through the results, you can use the IsValidForPurpose method to test if the item is suitable for the purpose specified. Each TrimMainObject has a purpose enumeration associated with it – use this enumeration to specify suitable purpose values for the object you are selecting.

Persisting a search

The TrimMainObjectSearch has a SearchAsXML property which will convert the search, filter and sort criteria to an XML string. This string is language-independent and is therefore useful if you wish to reuse the same search at some later stage, perhaps in a different locale. Item matching The TrimMainObjectSearch provides an ItemMatches method, which allows you to test whether an existing TrimMainObject matches the search criteria contained within the search.

Count

There are two counts FastCount and Count. FastCount may, in the case of the use of multiple levels of inherited ACLs, return -1 rather than an actual count. Count should always return a result accurate at the time it was called. Count will first try to use FastCount, if that fails it will fetch all the Records and then loop through them to count them. So it will often be as fast as FastCount but may be a lot slower (especially for large result sets). So, use FastCount only when you need to avoid a slow response and can accept an invalid response.

Sorting by User Defined Fields

User Defined Fields are not indexed in the database and are this not a part of the core sorting however it is possible to sort using them. Always take care when doing this as the lack of an index requires a the entire result set to be fetched and sorted in memory. On large result sets this can be very slow. To sort using a User Defined field use either GetEnumeratorSorted or GetResultAsUriArraySorted, as seen in this example:

TrimMainObjectSearch search = new TrimMainObjectSearch(database, BaseObjectTypes.Record);
search.SelectAll();
var enumerator = search.GetEnumeratorSorted(new PropertyOrFieldDef(new FieldDefinition(database, "Speed")), false);
while (enumerator.MoveNext())
{
Console.WriteLine(new Record(database, (TrimURI)enumerator.Current).Title);
}

Document Integrity Checker

The document store integrity check can now be executed from the SDK. The following class and enum are introduced for the document store checker

Type Name
Class DocumentStoreIntegrityCheckTask
Enum DocumentStoreIntegrityCheckCounter

Example: Running Document Store Integrity Check

string m_workPath = "C:\\tmp";
m_db = new Database();
m_db.Id = m_dbId;
m_db.WorkgroupServerName = "workgroupserver";
m_db.WorkgroupServerPort = 1137;
m_db.Connect();
ElectronicStore storeToCheck = (ElectronicStore)m_db.FindTrimObjectByName(BaseObjectTypes.ElectronicStore, "Main Document Store");
m_task = new DocumentStoreIntegrityCheckTask(storeToCheck);
m_task.FixErrors = true; //Set this to true if you want the checker to correct any error.
m_task.LogPath = m_workPath + "/StoreChecker.log"; //The path where the checker's log will be written to
try
{
m_task.Run();
//If you want to get a counter for a number of corrupted document
m_task.GetProgressCounter(DocumentStoreIntegrityCheckCounter.CorruptedDocuments);
//If you want to cancel the check
m_task.Cancel();
}
catch (TrimException trimException)
{
Debug.Print(trimException.Message);
}