Toll Free:

1800 889 7020

Using Pre-validation Plugins in Dynamics 365 to Prevent Invalid Record Imports

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:

  1. Practice Sales KPI: The main record being created.
  2. Employee: Lookup field on the KPI form; points to an employee.
  3. UPAM: Maintains a mapping between Users and Sub-Units.
  4. 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:

  1. The logged-in user (who is creating/importing the record) must have one or more UPAM entries mapping them to specific Sub-Units.
  2. 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:

  1. Identify Initiating User
    The system captures the User ID of the person performing the import or record creation.
  2. Fetch Allowed Sub-Units
    It queries the UPAM table to find all Sub-Units linked to this user.
  3. List Valid Employees
    Using the above Sub-Unit list, the plugin then queries the Employee table for records belonging to those Sub-Units.
  4. Cross-check Selected Employee
    The plugin finally checks if the Employee chosen in the current KPI record belongs to the fetched list.
  5. 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:

Plugin Registration
Plugin Registration tools

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”.
Practice Sales KPI using an EXCEL import

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.

Avatar photo

Yash Shah

Yash Shah is a seasoned technical architect at Aegis Softtech, bringing extensive experience in developing and leading enterprise-level projects. With a broad skill set in areas such as artificial intelligence, machine learning, microservices, and database management, he excels at crafting scalable and innovative solutions. Yash is highly adept at driving project success through technical expertise and strong leadership, ensuring the delivery of high-quality results across a wide range of industries.

Scroll to Top