Introduction
When dealing with bulk imports in Microsoft Dynamics 365 CRM Services, especially within Dynamics 365 CE (CRM) or Dataverse, data validation becomes critical. In our scenario, a business rule demands strict control over which Employees can be selected when creating a new Practice Sales KPI record—especially during data import operations, where users might unknowingly bypass standard UI-level checks.
Objective
The goal is to implement a Pre-validation stage plugin on the Practice Sales KPI table to prevent creation of records that violate data integrity rules around Employee and Sub-Unit associations. This helps avoid manual clean-ups post-import and ensures that each KPI entry aligns with access control based on UPAM mappings.
Tables Involved:
- Practice Sales KPI: The main record being created.
- Employee: Lookup field on the KPI form; points to an employee.
- UPAM: Maintains a mapping between Users and Sub-Units.
- Sub-Unit: Linked to both Employee and UPAM tables.
Business Rule/Requirement Explained:
When a user attempts to create a new Practice Sales KPI record, the plugin ensures the Employee selected meets both of the following criteria:
- The logged-in user (who is creating/importing the record) must have one or more UPAM entries mapping them to specific Sub-Units.
- The selected Employee must belong to one of these Sub-Units.
In simpler terms, the user is only allowed to assign Employees who are part of the Sub-Units they’re linked to via UPAM. If the selected employee falls outside this scope, the record creation is blocked with a meaningful error.
Validation Flow:
Here’s how the plugin logic flows at runtime:
- Identify Initiating User
The system captures the User ID of the person performing the import or record creation. - Fetch Allowed Sub-Units
It queries the UPAM table to find all Sub-Units linked to this user. - List Valid Employees
Using the above Sub-Unit list, the plugin then queries the Employee table for records belonging to those Sub-Units. - Cross-check Selected Employee
The plugin finally checks if the Employee chosen in the current KPI record belongs to the fetched list. - Error Handling
If there’s no match, the plugin throws an error like:
“The selected Employee doesn’t belong to any of the sub-units of the user’s UPAMs.”
This ensures data remains compliant with business logic—regardless of whether the record is created manually or via an automated import.
C# Pre-Validation Plugin Code:
using System;
using System.Linq;
using System.Collections.Generic;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
namespace MCS.DynamicsCRM.Plugins
{
/// <summary>
/// This Pre-Validation Plugin validates the selected Employee on Practice Sales KPI record
/// </summary>
public class PracticeSalesKPIValidations : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
tracingService.Trace("PracticeSalesKPIValidations plugin execution started.");
try
{
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
if (context.Stage != 10 || !(context.MessageName == "Create" || context.MessageName == "Update"))
{
tracingService.Trace($"Exiting plugin. Stage: {context.Stage}, Message: {context.MessageName}");
return;
}
if (!context.InputParameters.Contains("Target") || !(context.InputParameters["Target"] is Entity targetEntity))
{
tracingService.Trace("Target entity is not present or not of expected type.");
return;
}
EntityReference employeeRef = GetEmployeeReference(context, targetEntity, tracingService);
if (employeeRef == null)
{
tracingService.Trace("Employee reference is null. No validation required.");
return;
}
Guid initiatingUserId = context.InitiatingUserId;
IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
// Step 1: Fetch Sub-units via UPAMs
var subunitIds = GetSubunitIdsForUser(service, initiatingUserId, tracingService);
tracingService.Trace($"Sub-unit count fetched from UPAMs: {subunitIds.Count}");
if (subunitIds.Count == 0)
{
tracingService.Trace("No sub-units associated with UPAMs.");
return;
}
// Step 2: Fetch Employees under Sub-units
var validEmployeeIds = GetEmployeeIdsForSubunits(service, subunitIds, tracingService);
tracingService.Trace($"Employee count fetched for allowed sub-units: {validEmployeeIds.Count}");
// Step 3: Validation
if (!validEmployeeIds.Contains(employeeRef.Id))
{
string errorMsg = "The selected Employee doesn't belong to any of the sub-units of the user's UPAMs.";
tracingService.Trace($"Validation failed. EmployeeId: {employeeRef.Id}. Error: {errorMsg}");
throw new InvalidPluginExecutionException(errorMsg);
}
tracingService.Trace("Employee validation successful.");
}
catch (Exception ex)
{
tracingService.Trace($"Exception occurred: {ex.Message}");
tracingService.Trace($"Stack Trace: {ex.StackTrace}");
throw new InvalidPluginExecutionException("An error occurred in PracticeSalesKPIValidations plugin: " + ex.Message);
}
}
private EntityReference GetEmployeeReference(IPluginExecutionContext context, Entity targetEntity, ITracingService tracingService)
{
if (targetEntity.Contains("infy_employee"))
{
return targetEntity.GetAttributeValue<EntityReference>("infy_employee");
}
if (context.MessageName == "Update" && context.PreEntityImages.Contains("PreImage"))
{
tracingService.Trace("Employee value retrieved from PreImage.");
return context.PreEntityImages["PreImage"].GetAttributeValue<EntityReference>("infy_employee");
}
return null;
}
private List<Guid> GetSubunitIdsForUser(IOrganizationService service, Guid userId, ITracingService tracingService)
{
var subunitIds = new List<Guid>();
QueryExpression query = new QueryExpression("infy_upam")
{
ColumnSet = new ColumnSet("infy_subunit"),
Criteria = new FilterExpression
{
Conditions =
{
new ConditionExpression("infy_employee", ConditionOperator.Equal, userId)
}
}
};
EntityCollection results = service.RetrieveMultiple(query);
foreach (var entity in results.Entities)
{
if (entity.Contains("infy_subunit"))
{
var subunitRef = entity.GetAttributeValue<EntityReference>("infy_subunit");
if (subunitRef != null)
subunitIds.Add(subunitRef.Id);
}
}
return subunitIds.Distinct().ToList();
}
private List<Guid> GetEmployeeIdsForSubunits(IOrganizationService service, List<Guid> subunitIds, ITracingService tracingService)
{
var employeeIds = new List<Guid>();
QueryExpression query = new QueryExpression("infy_employee")
{
ColumnSet = new ColumnSet("infy_employeeid"),
Criteria = new FilterExpression
{
Conditions =
{
new ConditionExpression("infy_subunit", ConditionOperator.In, subunitIds.Cast<object>().ToArray())
}
}
};
EntityCollection results = service.RetrieveMultiple(query);
foreach (var entity in results.Entities)
{
employeeIds.Add(entity.Id);
}
return employeeIds;
}
}
}
Plugin Registration:
We register our plugin along with its pre-image as follows on the Plugin-Registration Tool:


UNIT TESTING
- While attempting to a create a “faulty” record for Practice Sales KPI using an EXCEL import, we verify that the system throws the following exception as expected – “The selected Employee doesn’t belong to any of the sub-units of the user’s UPAMs”.

Conclusion
Thus, by using a pre-validation plugin, we can stop incorrect KPI records right at the source—during import. This ensures users only select valid employees tied to their assigned sub-units. It’s a clean way to protect data integrity without relying on front-end checks alone.