Skip to main content

PreUpdate Trigger

  • Pre Update Trigger programs are executed before Protrak Type Instance is updated, before the platform code for instance update executes.
  • They are used to validate the instance details based on business requirements.
  • They are executed synchronously, i.e. in the same transaction as the instance update.
  • They are executed before standard validations like name uniqueness/ required attribute validations are executed by the platform code.

Coding guidelines

  • PreUpdate Trigger program is a class that must implement the interface IPreUpdateTriggerProgramAsync

    public interface IPreUpdateTriggerProgramAsync
    {
    Task<ProgramResult> RunAsync(Instance instance);
    }

    NOTE: Implement IPreUpdateTriggerProgramAsync with the RunAsync method to enable asynchronous, scalable trigger logic. Most internal service methods are now async, and their synchronous versions are deprecated and will be removed. Use RunAsync to ensure compatibility with these changes.

  • Protrak runtime passes Instance object having information about the instance being updated to the trigger. This object can be modified by the program code (pass by reference).
    • The Attribute[] instance.Attributes contains values passed to the service (typically values updated by user in the update form or those passed to the PUT request).
    • The values in this instance are not yet committed to the database.
    • To retrieve the committed data of the instance from the database (before the user made the update), the InstanceService.GetInstance should be used.
  • Program should return a ProgramResult object:
    • If IsSuccess is true, then the trigger execution is considered as successful.
    • If IsSuccess is false, then the trigger is considered as failed, subsequent code is not executed, and the DB transaction is rolled back.
    • If IsSuccess is false, ideally the string[] Errors should be populated with proper error messages which will be returned in API response, which can be displayed to end user.
  • As the trigger is executed before platform validations, the instance details may not have all valid data. For examples, all mandatory attributes may not be populated. While writing the program code, do not assume that all data will be valid. Write guard conditions to avoid runtime exceptions.

Typical use cases for Pre Update Trigger

  • Validate the instance being updated where some complex business logic is involved.
  • Validate the attribute values depend on some business logic, do not allow user to assign invalid value to the attribute.
  • Auto populate some attribute values on instance depend on some business logic.
  • Calculate and save an attribute value on the instance being updated using complex business logic.
    • For example, when business logic involves fetching related instance details linked to instance and use those details for some computations.
  • If any validations or calculations need to use a third-party service.

Anti-patterns or when not to use Pre Update Trigger

  • Prefer configuration over customization. Avoid using a program when configurable validations or computations are possible, for example. using expression attributes for calculation.
  • Do not use Pre Update trigger to make changes that are not reversible, like sending an email. If the update process fails, the transaction will be rolled back, instance will not be visible, but the email would have been sent already!
  • Do not user pre update trigger to do any hard operation that is on update certain attribute create instances in large amount

Sample Code

    private readonly string ATTRIBUTE_GEOGRAPHY = "Geography";
public async Task<ProgramResult> RunAsync(Instance instance)
{
bool programResult = true;
var errorMessages = new List<string>();

var attributes = new string[] { ATTRIBUTE_GEOGRAPHY };

//get instance from db with value of picklist attribute geography
var oldInstance = await InstanceService.GetInstanceAsync(instance.Id, attributes);

ValidateGeographyOnCustomer(instance, oldInstance, ref programResult, ref errorMessages);

if (programResult)
{
return new ProgramResult() { IsSuccess = true, Errors = null };
}
else
{
return new ProgramResult() { IsSuccess = false, Errors = errorMessages.ToArray() };
}
}

private void ValidateGeographyOnCustomer(Instance instance, Instance oldInstance,
ref bool programResult, ref List<string> errorMessages)
{
//get updated value of attribute geography from the passed instance
var attrGrography = GetAttribute(instance, ATTRIBUTE_GEOGRAPHY);

//if ArrayValue of geography in updated instance is null
if (attrGrography != null && attrGrography.ArrayValue == null)
{
//get value of attribute geography from the database, before the user updated it
var attrOldGeography = GetAttribute(oldInstance, ATTRIBUTE_GEOGRAPHY);

//if geography was not null in database but null in updated instance, it means user removed the value
if (attrOldGeography != null && attrOldGeography.ArrayValue != null)
{
programResult = false;
errorMessages.Add("User can not remove Geography from Customer " + instance.Name);
}
}
}

private Attribute GetAttribute(Instance instance, string attributeName)
{
return instance.Attributes.FirstOrDefault(a => a.Name == attributeName);
}

Note: The Run method (from IPreUpdateTriggerProgram) should only be used if the trigger logic is fully synchronous, does not involve any service/API calls, and consists only lightweight in-memory operations.