Instance Accessor Methods
Overview
The Instance class provides built-in getter and setter methods for all attribute types. Always use these methods instead of manually searching instance.Attributes with FirstOrDefault. They are null-safe and produce much simpler code.
Getter Methods
string value = instance.GetTextAttributeValue("AttributeName");
double? value = instance.GetNumericAttributeValue("AttributeName");
DateTime? value = instance.GetDateAttributeValue("AttributeName");
bool? value = instance.GetBooleanAttributeValue("AttributeName");
string[] values = instance.GetPicklistAttributeValue("AttributeName"); // null if not set
UserValue[] users = instance.GetUserAttributeValue("AttributeName");
ReferenceValue[] refs = instance.GetReferenceAttributeValue("AttributeName");
FileValue file = instance.GetAttachmentAttributeValue("AttributeName");
All getters return null when the attribute is absent or has no value — no NullReferenceException.
On RelatedInstance
RelatedInstance has the same getter methods as Instance. Access relation-level attributes via RelationAttributes with the AttributeArrayExtensions:
// Getters on a RelatedInstance — identical syntax to Instance
string name = relatedInstance.GetTextAttributeValue("Name");
double? amount = relatedInstance.GetNumericAttributeValue("Amount");
string[] status = relatedInstance.GetPicklistAttributeValue("Status");
// Relation-level attributes (via AttributeArrayExtensions)
using Prorigo.Protrak.API.Contracts.Extensions;
var roleOnRelation = relatedInstance.RelationAttributes.GetPicklistAttributeValue("PMSResourceRole");
Setter Methods
All typed setter methods create the attribute if it does not exist, or update it if it does — replacing the common pattern of "find or add to list":
instance.SetTextAttributeValue("AttributeName", "hello");
instance.SetNumericAttributeValue("AttributeName", 42.0);
instance.SetDateAttributeValue("AttributeName", DateTime.UtcNow);
instance.SetBooleanAttributeValue("AttributeName", true);
instance.SetPicklistAttributeValue("AttributeName", "Option A"); // single
instance.SetPicklistAttributeValue("AttributeName", "Opt A", "Opt B"); // multi
instance.SetUserAttributeValue("AttributeName", new UserValue[] { ... });
instance.SetReferenceAttributeValue("AttributeName", new ReferenceValue[] { ... });
instance.SetAttachmentAttributeValue("AttributeName", new FileValue { FileId = fileId, FileName = "report.pdf" });
Before / After Comparison
Reading a value
// ❌ Old (verbose, error-prone)
var attr = instance.Attributes.FirstOrDefault(a => a.Name == "ProjectScheduledStartDate");
var startDate = attr?.DateValue;
// ✅ New
var startDate = instance.GetDateAttributeValue("ProjectScheduledStartDate");
Reading a picklist (single value)
// ❌ Old
var attr = instance.Attributes.FirstOrDefault(a => a.Name == "Geography");
var geography = attr?.ArrayValue?.FirstOrDefault();
// ✅ New
var geography = instance.GetPicklistAttributeValue("Geography")?.FirstOrDefault();
Reading a reference
// ❌ Old
var attr = instance.Attributes.FirstOrDefault(a => a.Name == "ProjectToCustomerRef");
var customerId = attr?.ReferenceValues?.FirstOrDefault()?.Id;
// ✅ New
var customerId = instance.GetReferenceAttributeValue("ProjectToCustomerRef")?.FirstOrDefault()?.Id;
Setting a value in a Pre-trigger (mutating the incoming instance)
// ❌ Old (manual find-or-add)
var existing = instance.Attributes.FirstOrDefault(a => a.Name == "Geography");
if (existing != null)
existing.ArrayValue = new[] { geography };
else
{
var list = instance.Attributes.ToList();
list.Add(new Attribute { Name = "Geography", Type = AttributeType.Picklist, ArrayValue = new[] { geography } });
instance.Attributes = list.ToArray();
}
// ✅ New
instance.SetPicklistAttributeValue("Geography", geography);
Building an Instance for UpdateInstance
Use InstanceBuilder.ForUpdate to construct the update payload for UpdateInstance calls (PostCreate, PostUpdate, PromoteActionCommand).
using Prorigo.Protrak.API.Contracts.Builders;
// ✅ InstanceBuilder (preferred for UpdateInstance)
var updated = InstanceBuilder.ForUpdate(instanceId, "Project")
.SetPicklistAttributeValue("RAGDetails", "Green")
.SetBooleanAttributeValue("IsActive", true)
.SetTextAttributeValue("ProjectStatus", "Active")
.Build();
await InstanceService.UpdateInstanceAsync(updated, null);
The typed setters (
SetTextAttributeValue,SetPicklistAttributeValue, etc.) remain the right tool when mutating the existing incoming instance in Pre-triggers — no newInstanceobject is needed there.
Common Patterns Using Accessors
Validate a required field
var startDate = instance.GetDateAttributeValue("ScheduledStartDate");
if (startDate == null)
throw new Exception("Scheduled Start Date is required.");
Copy a value from a related instance
var customer = await InstanceService.GetInstanceAsync(customerId, new[] { "Geography" });
var geography = customer.GetPicklistAttributeValue("Geography")?.FirstOrDefault();
instance.SetPicklistAttributeValue("ProjectGeography", geography);
Read current user from a User attribute
var assignees = instance.GetUserAttributeValue("TaskAssignee");
if (assignees == null || assignees.Length == 0)
return new ProgramResult { IsSuccess = true }; // no assignee, skip
var assignee = assignees.First();
// assignee.UserId, assignee.UserName, assignee.UserEmail
Conditional update based on current value
var currentRag = instance.GetPicklistAttributeValue("RAGDetails")?.FirstOrDefault();
if (currentRag != "Red")
instance.SetPicklistAttributeValue("RAGDetails", "Red");