Monday, November 25, 2024

Migrating and Preserving ModifiedOn, ModifiedBy values of Dynamics 365/CRM

Out of the box there's no solution to keep the original source values of ModifiedOn and ModifiedBy when migrating data records to Dynamics 365. In an on-premises deployment you could accomplish the task by the unsupported query update on the destination database; but if you are migrating to cloud Dynamics 365 database access is not an option. In this article I'll walk you through step-by-step guide of one solution proven to work in migration projects. The migration scenario is to move data from an older Dynamics CRM system to online Dynamics 365/CRM; but this could work for other data sources wishing to migrate to Dynamics 365/CRM.

The steps are:
1. Create temporary fields in destination entity to hold the source values of ModifiedOn and ModifiedBy.
2. Write plug-in to set ModifiedOn/By values of destination entity.
3. Register the plug-in.
4. Map source fields to temporary fields and run the migration/import

Step 1: Create 2 temp fields new_ModifiedOn and new_ModifiedBy in the destination environment. Whatever method/tool of migration you will use, the source ModifiedOn will set value for destination new_ModifiedOn; and source ModifiedBy will set value for destination new_ModifiedBy.

Step 2: Write plug-in to set value of destination ModifiedOn and ModifiedBy. Core code for the plug-in is at the end.

Step 3: Register the plug-in, on create message and Pre-Operation.

Step 4: Map the fields for migration/import, like I mentioned in Step 1 - source ModifiedOn to destination new_ModifiedOn, and ModifiedBy to new_ModifiedBy

Now if you run the migration/import, the plug-in will pickup the original values in temp fields and set them for ModifiedOn/ModifiedBy upon creation of new record.

Here is my plugin:

using System;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;

namespace TXN.Plugin.PreserveRecordInfo
{
    public class PreCreateRecord : IPlugin
    {
        #region Secure/Unsecure Configuration Setup
        private string _secureConfig = null;
        private string _unsecureConfig = null;

        public PreCreateRecord(string unsecureConfig, string secureConfig)
        {
            _secureConfig = secureConfig;
            _unsecureConfig = unsecureConfig;
        }
        #endregion
        public void Execute(IServiceProvider serviceProvider)
        {
            ITracingService tracer = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
            IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
            IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            IOrganizationService service = factory.CreateOrganizationService(context.UserId);

            if (context == null) throw new InvalidPluginExecutionException("IPluginExecutionContext is null");
            if (service == null) throw new InvalidPluginExecutionException("IOrganizationService is null");
            if (tracer == null) throw new InvalidPluginExecutionException("ITracingService is null");

            tracer.Trace("TXN: message: " + context.MessageName + " | Timestamp: " + DateTime.Now);
            
            if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
            {
                Entity entity = (Entity)context.InputParameters["Target"];

                string _message = context.MessageName.ToUpper();
                string _entityLogical = entity.LogicalName;

                tracer.Trace("TXN: entity: " + entity.LogicalName + " | GUID: " + entity.Id.ToString());
                //TXN: Filter the message and your entities here
                if (!(_message == "CREATE" ) ||
                    !(_entityLogical == "knowledgearticle" || 
                      _entityLogical == "adobe_agreement" ||
                      _entityLogical == "phonecall" ||
                      _entityLogical == "fax" ||
                      _entityLogical == "letter" ||
                      _entityLogical == "appointment" ||
                      _entityLogical == "email" ||
                      _entityLogical == "task"))
                    return;

                try
                {
                    DateTime _initDate = new DateTime(2099, 12, 12);
                    DateTime _modifiedOn = _initDate;
                    string _modifiedBy = null;

                    if (entity.Contains("new_modifiedon") && entity.Attributes["new_modifiedon"] != null)
                    {
                        _modifiedOn = (DateTime)entity.Attributes["new_modifiedon"];
                        tracer.Trace("TXN: mapped new_modifiedon: " + _modifiedOn.ToString());
                    }

                    if (entity.Contains("new_modifiedbyname") && entity.Attributes["new_modifiedbyname"] != null)
                    {
                        _modifiedBy = (entity.Attributes["new_modifiedbyname"]).ToString();
                        tracer.Trace("TXN: mapped new_modifiedbyname: " + _modifiedBy);
                    }

                    //TXN: Set original modifiedon value in destination record
                    if(!_modifiedOn.Equals(_initDate))
                    {
                        entity.Attributes["modifiedon"] = _modifiedOn;
                        tracer.Trace("TXN: set modifiedon to: " + _modifiedOn.ToString());
                    }

                    //TXN: find new_modifiedbyname and set that user to destination record's modifiedby
                    if (_modifiedBy != null)
                    {
                        var fetchXml = @"<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'>
                              <entity name='systemuser'>
                                <attribute name='fullname' />
                                <attribute name='systemuserid' />
                                <filter type='and'>
                                  <condition attribute='fullname' operator='eq' value='{0}' />
                                </filter>
                              </entity>
                            </fetch>";
                        fetchXml = string.Format(fetchXml, _modifiedBy);
                        EntityCollection users = service.RetrieveMultiple(new FetchExpression(fetchXml));
                        if (users != null && users.Entities.Count > 0)
                        {
                            entity.Attributes["modifiedby"] = users.Entities[0].ToEntityReference();
                            tracer.Trace("TXN: set modifiedby to user: " + users.Entities[0].GetAttributeValue<string>("fullname"));
                        }
                    }
                }
                catch (Exception e)
                {
                    throw new InvalidPluginExecutionException(e.Message);
                }
            }
        }
    }
}

No comments:

Post a Comment