PreDelete Trigger
- PreDelete Trigger programs are executed before a Protrak Type Instance is deleted, before the platform code for instance deletion executes.
- They are used to validate, clean up, or manipulate related data based on business requirements.
- They are executed synchronously, i.e. in the same transaction as the instance deletion.
- They are executed before the platform removes the instance, so the instance and its relations are still available to the trigger.
Coding guidelines
-
PreDelete Trigger program is a class that must implement the interface
IPreDeleteTriggerProgramAsyncpublic interface IPreDeleteTriggerProgramAsync{Task<ProgramResult> RunAsync(Guid instanceId);}NOTE: Use
IPreDeleteTriggerProgramAsyncwith theRunAsyncmethod for new implementations so trigger logic stays asynchronous and consistent with the rest of the program surface. Most internal service methods are now async, and their synchronous versions are deprecated and will be removed. UseRunAsyncto ensure compatibility with these changes. -
Protrak runtime passes the
instanceIdof the instance being deleted to the trigger. -
If you need instance details, fetch them explicitly before making decisions.
-
Program should return a ProgramResult object:
- If
IsSuccessis true, then the trigger execution is considered as successful. - If
IsSuccessis false, then the trigger is considered as failed, subsequent code is not executed, and the DB transaction is rolled back. - If
IsSuccessis false, ideally thestring[] Errorsshould be populated with proper error messages which will be returned in API response, which can be displayed to end user.
- If
-
Because the trigger runs before deletion, related records can still be queried and removed or unlinked as needed.
Typical use cases for PreDelete Trigger
- Validate whether the instance can be deleted where some complex business logic is involved.
- Delete related child instances before the parent is removed.
- Unlink relations that should survive the parent deletion.
- Clean up access, audit, or reference data that must not be left orphaned.
- If any validations or cleanup steps need to use a third-party service or integration.
Anti-patterns or when not to use PreDelete Trigger
- Prefer configuration over customization. Avoid using a program when configurable validations or cleanup are possible.
- Do not use PreDelete trigger to make changes that are not reversible, like sending an email. If the delete process fails, the transaction will be rolled back, but the side effect would already have happened.
- Do not assume the instance has already been removed. PreDelete runs before deletion, so queries should be written with that timing in mind.
Sample Code
using Prorigo.Protrak.API.Contracts;
using Prorigo.Protrak.API.Services;
using Prorigo.Protrak.Programs;
using System;
using System.Threading.Tasks;
public class ProjectPreDeleteTriggerProgram : IPreDeleteTriggerProgramAsync
{
public IInstanceService InstanceService { get; set; }
public IRelationService RelationService { get; set; }
private readonly string RELATION_PROJECT_TO_TASK = "ProjectToTask";
private readonly string TASK_TYPE_NAME = "Task";
public async Task<ProgramResult> RunAsync(Guid instanceId)
{
var relatedTasks = await GetRelatedInstancesAsync(instanceId, TASK_TYPE_NAME, RELATION_PROJECT_TO_TASK);
if (relatedTasks?.Items != null && relatedTasks.Items.Count > 0)
{
return new ProgramResult
{
IsSuccess = false,
Errors = new[] { "Cannot delete project while related tasks still exist." }
};
}
return new ProgramResult { IsSuccess = true };
}
private async Task<PagedData<RelatedInstance>> GetRelatedInstancesAsync(
Guid instanceId, string typeName, string relationTypeName)
{
var query = new RelatedInstanceQuery
{
RelationFilters = new[]
{
new RelationFilter
{
TypeName = typeName,
RelationTypeName = relationTypeName,
RelationDirection = RelationDirection.To
}
}
};
return await InstanceService.GetRelatedInstancesAsync(instanceId, query);
}
}
Note:
The Run method (from IPreDeleteTriggerProgram) is still available for legacy synchronous implementations, but new triggers should use RunAsync unless you have a specific backward-compatibility reason not to.