1

The built-in Dynamics CRM connector for DEF has a pipeline step as part of syncing contacts from Dynamics to xConnect that reads the Dynamics contact ID and checks if there is an xDB Contact Identifier with that value. The logic is set on the "Resolve Contact Model by Dynamics Id from xConnect" step.

enter image description here

How can I change this process as follows?

  1. Try to resolve by the Dynamics ID (built in)
  2. Else if that is not resolved try to resolve contact model by matching the contact First name, Last name and Birth date facet fields

DEF 2.1 on Sitecore 9.1.1.

2

Maybe it is not the most elegant solution, but you can try. First of all you need to create a new pipeline step template which is inherited from Resolve Contact by Identifier from xConnect Pipeline Step template. Let's call it Extended Resolve Contact by Identifier from xConnect Pipeline Step. Add three extra fields to the identifier section:

  • FirstNameValueAccessor type: Droptree; source: query:./ancestor-or-self::[@@templateid='{327A381B-59F8-4E88-B331-BEBC7BD87E4E}']//descendant-or-self::[@@templateid='{ADD2D00A-784B-492F-94CB-971303DF4019}'])
  • LastNameValueAccessor type: Droptree; source: query:./ancestor-or-self::[@@templateid='{327A381B-59F8-4E88-B331-BEBC7BD87E4E}']//descendant-or-self::[@@templateid='{ADD2D00A-784B-492F-94CB-971303DF4019}'])
  • BirthDateValueAccessor type: Droptree; source: query:./ancestor-or-self::[@@templateid='{327A381B-59F8-4E88-B331-BEBC7BD87E4E}']//descendant-or-self::[@@templateid='{ADD2D00A-784B-492F-94CB-971303DF4019}'])

Then, we need to create a new CustomResolveXConnectContactStepConverter:

using Sitecore.DataExchange.Attributes;
using Sitecore.DataExchange.DataAccess;
using Sitecore.DataExchange.Models;
using Sitecore.DataExchange.Providers.XConnect.Converters.PipelineSteps;
using Sitecore.DataExchange.Repositories;
using Sitecore.Services.Core.Model;

namespace PipelineBatchRunner
{
    [SupportedIds(new string[] { "{TEMPLATE IF OF CustomResolveXConnectContactStepPROCESSOR }" })]
    public class CustomResolveXConnectContactStepConverter : ResolveXConnectContactByIdentifierStepConverter
    {
        public const string FirstNameValueAccessor = "FirstNameValueAccessor";
        public const string LastNameValueAccessor = "LastNameValueAccessor";
        public const string BirthDateValueAccessor = "BirthDateValueAccessor";

        public CustomResolveXConnectContactStepConverter(IItemModelRepository repository) : base(repository)
        {
        }

        protected override void AddPlugins(ItemModel source, PipelineStep pipelineStep)
        {
            base.AddPlugins(source, pipelineStep);

            ContactFallbackResolverSettings fallbackPlugin = new ContactFallbackResolverSettings()
            {

                FirstNameValueAccessor = this.ConvertReferenceToModel<IValueAccessor>(source, FirstNameValueAccessor),
                LastNameValueAccessor = this.ConvertReferenceToModel<IValueAccessor>(source, LastNameValueAccessor),
                BirthDateValueAccessor = this.ConvertReferenceToModel<IValueAccessor>(source, BirthDateValueAccessor),
            };
            pipelineStep.AddPlugin<ContactFallbackResolverSettings>(fallbackPlugin);

        }
    }
}

and CustomResolveXConnectContactStepProcessor:

using System;
using System.Linq;
using Sitecore.DataExchange.Attributes;
using Sitecore.DataExchange.Contexts;
using Sitecore.DataExchange.DataAccess;
using Sitecore.DataExchange.Models;
using Sitecore.DataExchange.Plugins;
using Sitecore.DataExchange.Providers.XConnect;
using Sitecore.DataExchange.Providers.XConnect.Plugins;
using Sitecore.DataExchange.Providers.XConnect.Processors.PipelineSteps;
using Sitecore.Services.Core.Diagnostics;
using Sitecore.XConnect;
using Sitecore.XConnect.Client;
using Sitecore.XConnect.Collection.Model;

namespace PipelineBatchRunner
{
    [RequiredPipelineStepPlugins(new Type[] { typeof(XConnectContactIdentifierSettings), typeof(LoadInteractionsSettings), typeof(RetrySettings), typeof(ContactFallbackResolverSettings) })]
    public class CustomResolveXConnectContactStepProcessor : ResolveXConnectContactByIdentifierStepProcessor
    {
        protected override object FindExistingObject(Sitecore.XConnect.IXdbContext client, IXConnectClientHelper clientHelper,
            string identifierValue, PipelineStep pipelineStep, PipelineContext pipelineContext, ILogger logger)
        {
            var contact = base.FindExistingObject(client, clientHelper, identifierValue, pipelineStep, pipelineContext, logger);

            if (contact == null)
            {
                ContactFallbackResolverSettings plugin = pipelineStep.GetPlugin<ContactFallbackResolverSettings>();
                if (plugin == null)
                    return null;

                ContactExpandOptions contactExpandOptions = GetContactExpandOptions(pipelineStep, pipelineContext, logger);
                Contact contact2 = (Contact)null;

                try
                {
                    var firstName = ReadFirstName(plugin, pipelineStep, pipelineContext, logger);
                    var lastName = ReadLastName(plugin, pipelineStep, pipelineContext, logger);
                    var birthDate = ReadBirthDate(plugin, pipelineStep, pipelineContext, logger);

                    var queryable = client.Contacts.Where(c =>
                            c.GetFacet<PersonalInformation>(Sitecore.XConnect.Collection.Model.CollectionModel.FacetKeys
                                .PersonalInformation).FirstName == firstName &&
                            c.GetFacet<PersonalInformation>(Sitecore.XConnect.Collection.Model.CollectionModel.FacetKeys
                                .PersonalInformation).LastName == lastName &&
                            c.GetFacet<PersonalInformation>(Sitecore.XConnect.Collection.Model.CollectionModel.FacetKeys
                                .PersonalInformation).Birthdate == birthDate)
                        .WithExpandOptions(contactExpandOptions);

                    var enumerator = queryable.GetBatchEnumeratorSync(1);

                    while (enumerator.MoveNext())
                    {
                        return enumerator.Current;
                    }
                }
                catch (XdbExecutionException ex)
                {
                    // Handle exception

                    return null;
                }
            }

            return contact;
        }

        protected virtual string ReadFirstName(ContactFallbackResolverSettings plugin, PipelineStep pipelineStep, PipelineContext pipelineContext, ILogger logger)
        {
            XConnectContactIdentifierSettings xConnectContactIdentifierSettings = pipelineStep.GetPlugin<XConnectContactIdentifierSettings>();
            object fromPipelineContext = this.GetObjectFromPipelineContext(xConnectContactIdentifierSettings.ContactIdentificationLevelObjectLocation, pipelineContext, logger);
            if (fromPipelineContext != null)
            {
                var firstNameValueAccessor = plugin.FirstNameValueAccessor;
                if (firstNameValueAccessor != null)
                {
                    IValueReader valueReader = firstNameValueAccessor.ValueReader;
                    if (valueReader != null)
                    {
                        DataAccessContext context = new DataAccessContext();
                        ReadResult readResult = valueReader.Read(fromPipelineContext, context);
                        if (readResult != null && readResult.WasValueRead)
                        {
                            return readResult.ReadValue as string;
                        }
                    }
                }
            }

            return null;
        }

        protected virtual string ReadLastName(ContactFallbackResolverSettings plugin, PipelineStep pipelineStep, PipelineContext pipelineContext, ILogger logger)
        {
            XConnectContactIdentifierSettings xConnectContactIdentifierSettings = pipelineStep.GetPlugin<XConnectContactIdentifierSettings>();
            object fromPipelineContext = this.GetObjectFromPipelineContext(xConnectContactIdentifierSettings.ContactIdentificationLevelObjectLocation, pipelineContext, logger);
            if (fromPipelineContext != null)
            {
                var firstNameValueAccessor = plugin.LastNameValueAccessor;
                if (firstNameValueAccessor != null)
                {
                    IValueReader valueReader = firstNameValueAccessor.ValueReader;
                    if (valueReader != null)
                    {
                        DataAccessContext context = new DataAccessContext();
                        ReadResult readResult = valueReader.Read(fromPipelineContext, context);
                        if (readResult != null && readResult.WasValueRead)
                        {
                            return readResult.ReadValue as string;
                        }
                    }
                }
            }

            return null;
        }

        protected virtual DateTime? ReadBirthDate(ContactFallbackResolverSettings plugin, PipelineStep pipelineStep, PipelineContext pipelineContext, ILogger logger)
        {
            XConnectContactIdentifierSettings xConnectContactIdentifierSettings = pipelineStep.GetPlugin<XConnectContactIdentifierSettings>();
            object fromPipelineContext = this.GetObjectFromPipelineContext(xConnectContactIdentifierSettings.ContactIdentificationLevelObjectLocation, pipelineContext, logger);
            if (fromPipelineContext != null)
            {
                var firstNameValueAccessor = plugin.BirthDateValueAccessor;
                if (firstNameValueAccessor != null)
                {
                    IValueReader valueReader = firstNameValueAccessor.ValueReader;
                    if (valueReader != null)
                    {
                        DataAccessContext context = new DataAccessContext();
                        ReadResult readResult = valueReader.Read(fromPipelineContext, context);
                        if (readResult != null && readResult.WasValueRead)
                        {
                            return readResult.ReadValue as DateTime?;
                        }
                    }
                }
            }

            return null;
        }

    }
}

ContactFallbackResolverSettings.cs

namespace PipelineBatchRunner
{
    public class ContactFallbackResolverSettings : Sitecore.DataExchange.IPlugin
    {
        public Sitecore.DataExchange.DataAccess.IValueAccessor FirstNameValueAccessor { get; set; }
        public Sitecore.DataExchange.DataAccess.IValueAccessor LastNameValueAccessor { get; set; }
        public Sitecore.DataExchange.DataAccess.IValueAccessor BirthDateValueAccessor { get; set; }
    }
}

The code above overrides a standard Resolve Contact by Identifier from xConnect Pipeline Step behavior. If contact is not resolved by identifier, the extended pipeline step will try to resolve it by First Name, Last Name and Birth Date.

Don't forget to update with corresponding namespaces _StandardValues of the Converter Type and Processor Type fields for the newly created pipeline step.

You probably can optimize the code. I've only tried to explain an idea.

-1

As per my understanding, while we sync contact CRM to Sitecore it store Dynamics.ContactId source in ContactIdentifiers table.

enter image description here

Resolve Contact Model by Dynamics Id from xConnect pipeline resolve contact on the basis of Identifier which stored in ContactIdentifiers table.

If you want to resolve contact on the basis of First name, Last name and Birth date then you need to check that pipeline which is creating Dynamics.ContactId identifier.

  • Thanks for the comment but I had already highlighted this pipeline in the question. – Mark Gibbons Sep 2 at 6:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.