Object Manager - A Primer
The Object Manager API manages Relativity Dynamic Objects (RDOs) and Documents. It's designed with a focus on consistency and flexibility across all endpoints. The primary use is performing CRUDQ operations. The Object Manager also encompasses some auxiliary features. These features include execution of mass operations, large volume object exports, progress and cancellation tokens, and interacting with event handlers.
Some sample use cases:
- Create RDOs and set values on their associated fields.
- Update fields on Document objects or RDOs. Modify the field types currently available on a Relativity object, including currency, date, user, and others.
- Retrieve a list of dependent objects prior to deleting a specific object. Delete one or more objects.
- Perform mass CRUD operations against Documents or RDOs.
- Query for Workspaces, Documents, RDOs, and system types.
- Exporting objects.
The Object Manager needs the following references:
- Relativity.Services.ServiceProxy - classes used to connect to Object Manager outside extensibility points (use this if you are running your program outside of Relativity).
- kCura.[your extensibility point] - contains Helpers classes used to connect to the APIs inside extensibility points. Use this if you are running your program within Relativity.
- Examples
- kCura.EventHandler.dll
- Relativity.CustomPages.dll
- kCura.Agent.dll
- Examples
- Relativity.Services.Objects.DataContracts - classes used for basic Object Manager functionality, such as passing data to the APIs.
- Relativity.Services.Objects - classes used to establish an instance of Object Manager.
The Nuget package Relativity.ObjectManager.SDK contains the necessary references for running Object Manager outside an extensibility point (an agent, custom page, or even handler). To connect Object Manager inside Relativity, you will need to add the relevant extensibility point package.
Creating a connection to the API
Create a connection to the Object Manager API by first determining if the program will run within a Relativity extensibility point (an agent, custom page, or event handler) or in an external application. If your program runs in an extensibility point, you can connect to Object Manager using the Helper class in the kCura.[your extensibility point] library.
1 2 3 4 5 | var serviceManager = ConnectionHelper.Helper().GetServicesManager(); using (IObjectManager objectManager = serviceManager.CreateProxy<IObjectManager>()) { // Do work with the objectManager instance. } |
If your program runs outside of a Relativity extensibility point, you can connect to the Object Manager using a Service Factory. Make sure to replace the URI, username, and password with values for your environment. See Login Profiles for more authentication options. Also verify if you are using http or https.
1 2 3 4 5 6 7 8 | Credentials credentials = new UsernamePasswordCredentials( "exampleusername@relativity.com" , "examplepassword" ); ServiceFactorySettings settings = new ServiceFactorySettings(restUri, credentials); ServiceFactory factory = new ServiceFactory(settings); using (IObjectManager objectManager = factory.CreateProxy<IObjectManager>()) { // Do work with the objectManager instance. } |
Deciding between .NET or REST API
Object Manager is accessible via both REST and .NET. The functionality of the two are similar but some considerations are needed:
- If fluent in .NET, .NET is the best choice. You can use C# or VB.Net for projects.
- REST is often used for mobile development or if something platform and language independent is required.
- The .NET API allows accessing tokens to use to control the progress of long operations. This functionality is not in REST Object Manager.
Using CRUDQ Operations in Object Manager
Create - CreateAsync is the preferred method for creating single objects.
This method supports creating single objects and supports the firing of PreSave and PostSave event handlers. The results contain the status of every fired event handler. Object Manager is the preferred API for managing and aggregating the responses for all associated event handlers.
Read - ReadAsync returns the values for specified fields on an object.
The result includes all PreLoad event handler statues and an ObjectVersionToken used in overwrite protection.
Update - UpdateAsync will alter specified fields.
The results include all statuses of any PreSave and PostSave event handlers fired.
Delete - DeleteAsync deletes an object, all child objects, and unlink all associations made via single or multiple object fields. To preview what changes will occur as the result of delete call, first make a call to the Dependency List API.
This method also fires PreCascadeDelete and PreDelete handlers. PreCascade always fires first.
Query - QueryAsync and QuerySlimAsync can both be used to retrieve objects based on a set of conditions.
More information on when to use QueryAsync or QuerySlimasync is provided below.
Deciding between QueryAsync or QuerySlimAsync
Query and QuerySlim have identical inputs and both return a set of objects from Relativity. The difference between the two is the amount of data and the formatting of the data returned. The Query endpoint returns more detailed information about the returned field-value pairs. QuerySlim returns less information and is often the preferred method for consumption via HTTP for performance reasons.
A difference is how fields and values are represented between the two. With Query, each object can be understood in its entirety without any additional context, but the consequence is that Field definitions are repeated across each element.
Use QueryAsync if you:
- are processing the return objects.
- require the GUIDs associated with each object.
- QueryAsync is often preferred if you are using the .NET proxy.
Use QuerySlimAsync if you:
- have an application that is sensitive to the payload size.
- are planning to use the results to a UI.
- QuerySlimAsync is often preferred if you are using REST.
Sample Call in .NET and REST
Below is the same call performed against the .NET and REST endpoints. In this example the QueryAsync endpoint is used to return a list of RDOs that satisfy a compound condition. The list is then sorted in descending order by name. Three fields on those RDOs are specified to be returned.
In the following sample fields are passed to the API by GUID, ArtifiactID, and Name. This is done for reference purposes. Any of these will work, although Relativity recommends using GUIDs for application development as these are constant across every instance of Relativity and allow applications to be exported.
.NET Walkthrough
- Create a function to hold your asynchronous calls.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | class Program { static void Main( string [] args) { try { MainAsync(args).Wait(); } catch (exception e) { Console.WriteLine(e); } } public static async Task MainAsync( string [] args) { // Call your asynchronous functions here! CRUDQSamples objectManager = new CRUDQSamples(); var result = await objectManager.QueryAsync(); } } |
2. Create a help function to return a Service Factory that can be used to connect to Object Manager.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | using System; using System.Net.Http; using Relativity.Services.ServiceProxy; class HttpRequests { public ServiceFactory getNewServiceFactory() { Uri keplerUri = new Uri(restServerAddress); Uri servicesUri = new Uri(rsapiAddress); ServiceFactorySettings settings = new ServiceFactorySettings( servicesUri, keplerUri, new Relativity.Services.ServiceProxy.UsernamePasswordCredentials( "username" , "password" )); Relativity.Services.ServiceProxy.ServiceFactory _serviceFactory = new Relativity.Services.ServiceProxy.ServiceFactory(settings); return _serviceFactory; } } |
3. Construct your query and pass it to the API.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | using System; using System.Collections.Generic; using System.Threading.Tasks; using Relativity.Services.ServiceProxy; using Relativity.Services.Objects.DataContracts; using Relativity.Services.Objects; class CRUDQSamples { HttpRequests createClients = new HttpRequests(); //This query performs an asynchronous operation when it sends the QueryRequest to Relativity. To accommodate this, we will describe our function as ‘async’ and it will return a Task. Remember to await this function when you invoke it. public async Task<Relativity.Services.Objects.DataContracts.QueryResult> QueryAsync() { ServiceFactory _serviceFactory = createClients.getNewServiceFactory(); // Set up the primary parameters of the search int workspaceId = 1017385; // Pass the artifact type ID. This is not the same number as the ArtifactID! It is the ID for object type (Documents are always artifact typeID 10) var artifactTypeId = new Relativity.Services.Objects.DataContracts.ObjectTypeRef {ArtifactTypeID = 1000043}; const int indexOfFirstDocumentInResult = 1; const int lengthOfResults = 100; // Conditions are passed in a SQL like string that allows for composite queries. This queries text, whole number and User field types. string condition = "('Name' LIKE 'SampleString' AND 'SampleWholeNumberField' == 4) OR ('UserField' LIKE USER 'Last, First')" ; var sort = new global::Relativity.Services.Objects.DataContracts.Sort() { Direction = global::Relativity.Services.Objects.DataContracts.SortEnum.Descending, FieldIdentifier = new FieldRef { Guid = new Guid( "DB7A78AC-44C1-4E45-9A49-AED1D1CE24B0" ) } }; // Create a request object with the parameters var queryRequest = new QueryRequest() { Condition = condition, // FieldRef objects can identify which fields to return by Guid, ArtifactID, or Name Fields = new List<FieldRef> { new FieldRef { Guid = new Guid( "9427DDDB-1096-4192-8085-302B3268AB22" ) }, new FieldRef { ArtifactID = 1039519 }, new FieldRef { Name = "FieldName" } }, RelationalField = null , ObjectType = artifactTypeId, Sorts = new List<Relativity.Services.Objects.DataContracts.Sort> { sort }, }; try { // Pass your parameters to Object Manager in the data access layer using (IObjectManager objectManager = _serviceFactory.CreateProxy<IObjectManager>()) { return await objectManager.QueryAsync(workspaceId, queryRequest, indexOfFirstDocumentInResult, lengthOfResults); } } catch (Exception e) { Console.Write(e); return null ; } } } |
REST walkthrough
1. Set your endpoint to make a query send. Below makes a Post request to the below endpoint.
Endpoint - {your Relativity URI}/Relativity.REST/api/Relativity.Objects/workspace/{target WorkspaceID}/ object /query |
2. Make your headers.
- Authorization: Base64 encoding of your Username and Password in the format ‘Username:Password’ (Don’t forget the colon)
- X-CSRF-Header: Leave this blank
- Content-Type: Set to “application/json”
3. Request body by constructing a JSON object containing your parameters
- Notes: Sort Directions 1 is descending and 0 is ascending.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | { "Request" :{ "ObjectType" :{ "Guid" : "60A89BD1-87D7-46B3-B8FD-0C701FE379BD" }, "fields" :[ { "Guid" : "9427DDDB-1096-4192-8085-302B3268AB22" }, { "ArtifactID" :1039519 }, { "Name" : "Winner" } ], "condition" : "('Name' LIKE 'SampleString' AND 'SampleWholeNumberField' == 4) OR ('UserField' LIKE USER 'LastName, FirstName')" , "sorts" :[{ "Direction" :1, "FieldIdentifier" : { "Guid" : "DB7A78AC-44C1-4E45-9A49-AED1D1CE24B0" } }] }, "start" :0, "length" :100 } |
For some related reading, see the following articles:
Join the Relativity DevHelp Forums at https://devhelp.relativity.com/.