Showing posts with label Data Import. Show all posts
Showing posts with label Data Import. Show all posts

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);
                }
            }
        }
    }
}