/
Salesforce Field Values in Sitecore Default Facets
Salesforce Field Values in Sitecore Default Facets
The following code syncs Salesforce field values io Sitecore xDB facets for Sitecore 9.x. You may need to modify it for other versions. The class can be executed by the Sitecore scheduler.
using System; using FuseIT.Sitecore.SalesforceConnector.Entities; using FuseIT.Sitecore.SalesforceConnector.SalesforcePartner; using FuseIT.Sitecore.SalesforceConnector; using FuseIT.Sitecore.Salesforce; using FuseIT.Sitecore.SalesforceConnector.DataSource; using FuseIT.Sitecore.SalesforceConnector.Soql; using MoreLinq; using Sitecore.Configuration; using Sitecore.Diagnostics; using Sitecore.XConnect; using Sitecore.XConnect.Client; using Sitecore.XConnect.Client.Configuration; using Sitecore.XConnect.Collection.Model; using System.Collections.Generic; using System.Linq; using S4SContact = FuseIT.Sitecore.SalesforceConnector.Entities.Contact; using S4SEntityBase = FuseIT.Sitecore.SalesforceConnector.Entities.EntityBase; using S4SLead = FuseIT.Sitecore.SalesforceConnector.Entities.Contact; namespace FuseIT.S4S.WebToSalesforce { public class BulkUpdateXdbFromSalesforce { /// <summary> /// Get leads and contacts in Salesforce with a Sitecore visitor id that have been changed recently and updates the xDB /// </summary> public static void BulkUpdateRecentlyChanged() { Log.Info("Bulk updating xDB with recently changed Salesforce contacts and leads", typeof(BulkUpdateXdbFromSalesforce)); var salesforceSession = new SalesforceSession("S4SConnString"); var leadDataSource = new LeadDataSource(salesforceSession); leadDataSource.AddDataSourceFilter(visitorIdField, ComparisonOperator.NotEquals, null); leadDataSource.AddDataSourceFilter("isConverted", ComparisonOperator.Equals, false); leadDataSource.AddDataSourceFilter("LastModifiedDate", ComparisonOperator.GreaterOrEqual, DateTime.Now.AddDays(-2)); var contactDataSource = new ContactDataSource(salesforceSession); contactDataSource.AddDataSourceFilter(visitorIdField, ComparisonOperator.NotEquals, null); contactDataSource.AddDataSourceFilter("LastModifiedDate", ComparisonOperator.GreaterOrEqual, DateTime.Now.AddDays(-2)); BulkUpdate(leadDataSource, contactDataSource); } /// <summary> /// Takes every lead and contact in Salesforce with a Sitecore visitor id and updates the xDB /// </summary> public static void BulkUpdateAll() { Log.Info("Bulk updating xDB with all Salesforce contacts and leads", typeof(BulkUpdateXdbFromSalesforce)); var salesforceSession = new SalesforceSession("S4SConnString"); var leadDataSource = new LeadDataSource(salesforceSession); leadDataSource.AddDataSourceFilter(visitorIdField, ComparisonOperator.NotEquals, null); leadDataSource.AddDataSourceFilter("isConverted", ComparisonOperator.Equals, false); var contactDataSource = new ContactDataSource(salesforceSession); contactDataSource.AddDataSourceFilter(visitorIdField, ComparisonOperator.NotEquals, null); BulkUpdate(leadDataSource, contactDataSource); } static void BulkUpdate(LeadDataSource leadDataSource, ContactDataSource contactDataSource) { // "Id" is required by S4S when requesting existing Salesforce objects but unused by this code var fields = new[] { "Id", visitorIdField, "FirstName", "LastName", "Email" }; var leads = LazyGetEntities<S4SLead>(leadDataSource, fields) .Select(lead => lead.InternalFields); var contacts = LazyGetEntities<S4SContact>(contactDataSource, fields) .Select(contact => contact.InternalFields); var leadsOrContacts = contacts.Concat(leads) .Select(internalFields => new LeadContactUnion { VisitorId = Guid.Parse(internalFields[visitorIdField]), FirstName = internalFields["FirstName"], LastName = internalFields["LastName"], Email = internalFields["Email"] }); var contactExpandOptions = new ContactExpandOptions(PersonalInformation.DefaultFacetKey, EmailAddressList.DefaultFacetKey); using (var xconnect = SitecoreXConnectClientConfiguration.GetClient()) { foreach (var batch in leadsOrContacts.Batch(100).ToList()) { // Get all xDB contacts associated with the visitor id stored in Salesforce (in this batch). // These are the xDB contacts we want to ensure are up to date var contactReferences = ContactReferenceList(batch, aliasIdentifierSource, leadOrContact => leadOrContact.VisitorId.ToString()); var xcontactsDictionary = GetMultipleContacts(xconnect, contactReferences, contactExpandOptions) .ToDictionary(contact => Guid.Parse(Identifier(contact, aliasIdentifierSource))); // Get all contacts in this batch associated with a list manager for later merging // When importing contacts in Sitecore's list manager, they're created with a ListManager identifier, and not automatically // associated with existing contacts sharing the same email. The editor can choose another identifierSource and identifier, so Sitecore // can merge things correctly var listManagerXcontactReferences = ContactReferenceList(batch, listManagerIdentifierSource, leadOrContact => leadOrContact.Email); var listManagerXcontacts = GetMultipleContacts(xconnect, listManagerXcontactReferences, contactExpandOptions) .ToLookup(contact => Identifier(contact, listManagerIdentifierSource)); // Get all S4S contacts matching the emails in this batch. This is so we can check if we're about to update the S4S // identifier to a value that is already assigned to another contact. Identifiers must be unique. // Salesforce allows for leads/contacts with same email addresss, but since Sitecore identifiers must be unique, // two xDB contacts cannot share the same identifier. var s4sXcontactReferences = ContactReferenceList(batch, s4sIdentifierSource, leadOrContact => leadOrContact.Email); var s4sXcontacts = GetMultipleContacts(xconnect, s4sXcontactReferences, contactExpandOptions) .ToLookup(contact => Identifier(contact, s4sIdentifierSource)); foreach (var leadOrContact in batch) { if (xcontactsDictionary.TryGetValue(leadOrContact.VisitorId, out Contact xcontact)) { // Update personal info var personalInfo = xcontact.GetFacet<PersonalInformation>(PersonalInformation.DefaultFacetKey) ?? new PersonalInformation(); personalInfo.FirstName = leadOrContact.FirstName; personalInfo.LastName = leadOrContact.LastName; xconnect.SetFacet(xcontact, PersonalInformation.DefaultFacetKey, personalInfo); // Update email var emailAddresses = xcontact.GetFacet<EmailAddressList>(EmailAddressList.DefaultFacetKey); if (emailAddresses != null) { emailAddresses.PreferredEmail = new EmailAddress(leadOrContact.Email, true); emailAddresses.PreferredKey = "Work"; } else { emailAddresses = new EmailAddressList(new EmailAddress(leadOrContact.Email, true), "Work"); } xconnect.SetFacet(xcontact, EmailAddressList.DefaultFacetKey, emailAddresses); // Merge imported contacts (in the list manager) with this contact var otherListManagerXcontacts = listManagerXcontacts[leadOrContact.Email] .Where(listManagerXcontact => listManagerXcontact.Id != xcontact.Id) .ToList(); foreach (var listManagerXcontact in otherListManagerXcontacts) { var isS4SContact = listManagerXcontact.Identifiers.Any(identifier => identifier.Source == s4sIdentifierSource); if (!isS4SContact) xconnect.MergeContacts(listManagerXcontact, xcontact); } // Check if email has changed and if so, update S4S identifier. var otherS4SXcontacts = s4sXcontacts[leadOrContact.Email] .Where(s4sXcontact => s4sXcontact.Id != xcontact.Id) .ToList(); // Even though the existing S4S modifier might be different from what is stored, we cannot update it if another // xDB contact uses the same identifier. Duplicate identifiers are not allowed. if (!otherS4SXcontacts.Any()) { var existingS4SIdentifier = NullableIdentifier(xcontact, s4sIdentifierSource); if (existingS4SIdentifier != leadOrContact.Email) { if (!string.IsNullOrEmpty(existingS4SIdentifier)) xconnect.RemoveContactIdentifier(xcontact, s4sIdentifierSource, existingS4SIdentifier); xconnect.AddContactIdentifier(xcontact, new ContactIdentifier(s4sIdentifierSource, leadOrContact.Email, ContactIdentifierType.Known)); } } // Check if email has changed and if so, update ListManager identifier... var existingListManagerIdentifier = NullableIdentifier(xcontact, listManagerIdentifierSource); if (existingListManagerIdentifier != leadOrContact.Email) { if (!string.IsNullOrEmpty(existingListManagerIdentifier)) xconnect.RemoveContactIdentifier(xcontact, listManagerIdentifierSource, existingListManagerIdentifier); // ... but only if we're not merging with other xDB contacts, in which case their ListManager identifier will be transfered if (!otherListManagerXcontacts.Any()) xconnect.AddContactIdentifier(xcontact, new ContactIdentifier(listManagerIdentifierSource, leadOrContact.Email, ContactIdentifierType.Known)); } } } xconnect.Submit(); } } Log.Info("Bulk update succeeded", typeof(BulkUpdateXdbFromSalesforce)); } // Sitecore agent needs to be able to initialize an object and call an instance member public void SitecoreAgentBulkUpdateRecentlyChanged() => BulkUpdateAll(); class LeadContactUnion { public string FirstName; public string LastName; public string Email; public Guid VisitorId; } #region Constants static string s4sIdentifierSource = Settings.GetSetting("S4S.Analytics.IdentifierSource", "S4S"); const string visitorIdField = "FuseITAnalytics__SitecoreVisitorId__c"; // The alias identifier is what's stored in Salesforce by S4S as Sitecore Visitor Id const string aliasIdentifierSource = Constants.AliasIdentifierSource; const string listManagerIdentifierSource = Sitecore.ListManagement.Constants.ListManagerIdentifierSource; #endregion #region Helper methods /// <summary> /// Throws if identifier doesn't exist /// </summary> static string Identifier(Contact contact, string identifierSource) => contact.Identifiers.First(identifier => identifier.Source == identifierSource).Identifier; static string NullableIdentifier(Contact contact, string identifierSource) => contact.Identifiers.FirstOrDefault(identifier => identifier.Source == identifierSource)?.Identifier; static IReadOnlyCollection<IdentifiedContactReference> ContactReferenceList<T>( IEnumerable<T> list, string identifierSource, Func<T, string> identifier) => list.Select(t => new IdentifiedContactReference(identifierSource, identifier(t))).ToList(); static IEnumerable<Contact> GetMultipleContacts( XConnectClient xconnect, IReadOnlyCollection<IEntityReference<Contact>> contactReferences, ContactExpandOptions expandOptions) => XConnectSynchronousExtensions.SuspendContextLock(() => xconnect.GetContactsAsync(contactReferences, expandOptions)) .Where(lookupResult => lookupResult.Exists) .Select(lookupResult => lookupResult.Entity); static IEnumerable<T> LazyGetEntities<T>(SalesforceDataSource dataSource, params string[] fields) where T : S4SEntityBase { var pager = dataSource.GetPager(fields); for (var index = 0; index < pager.TotalRecordCount; index++) yield return dataSource.EntityFromQueryResultPager<T>(pager, index); } #endregion } }
, multiple selections available,
Related content
Sync Salesforce field values into Sitecore facets
Sync Salesforce field values into Sitecore facets
Read with this
Salesforce Field Values in Sitecore Facets for Rules Based Personalization
Salesforce Field Values in Sitecore Facets for Rules Based Personalization
More like this
Facet Configuration
Facet Configuration
Read with this
Update Facets
Update Facets
More like this
S4S List Builder for Sitecore 9.0 - 9.2
S4S List Builder for Sitecore 9.0 - 9.2
More like this
S4S List Builder for Sitecore 9.3 and above
S4S List Builder for Sitecore 9.3 and above
Read with this