For clarity to the reader: the following answer and code snippets are for Sitecore 8.2.X only and will not work for Sitecore 9.X.
We got in contact with Sitecore Support who confirmed that it isn't supported for separate threads using their API. We were able to get something working by using a workaround. This hasn't had full code tidy up done yet - just a PoC.
We are able to run this using Task.Run successfully and have data pushed to XDB. It also handles DB locking scenarios if that is of interest, otherwise, you can ignore it.
namespace Sample.Foundation.Accounts.Respositories
{
[Service(typeof(IUpdateAnalyticsRepository))]
public class UpdateAnalyticsRepository: IUpdateAnalyticsRepository
{
private ContactManager _manager = Sitecore.Configuration.Factory.CreateObject("tracking/contactManager", true) as ContactManager;
private ContactRepository _repository = Sitecore.Configuration.Factory.CreateObject("tracking/contactRepository", true) as ContactRepository;
public ContactManager XdbContactManager
{
get { return _manager; }
}
public ContactRepository XdbContactRepository
{
get { return _repository; }
}
public Contact GetXdbContact(string identifier)
{
var contact = XdbContactRepository.LoadContactReadOnly(identifier);
if (contact != null) return contact;
var leaseOwner = new LeaseOwner(GetType() + Guid.NewGuid().ToString(), LeaseOwnerType.OutOfRequestWorker);
contact = XdbContactRepository.CreateContact(Guid.NewGuid());
if (contact == null) return null;
contact.Identifiers.Identifier = identifier;
//Setting these values allows processing and aggregation to process this contact without erroring.
contact.System.Value = 0;
contact.System.VisitCount = 0;
//MAKE OTHER Contact Changes here too if you need to on Create.
XdbContactRepository.SaveContact(contact, new ContactSaveOptions(true, leaseOwner));
return contact;
}
public void UpdateXdbContact(string identifier, string firstName, string lastName, string email)
{
try
{
var currentContact = GetXdbContact(identifier);
if (currentContact == null)
return;
var lockResult = XdbContactManager.TryLoadContact(currentContact.ContactId);
Contact updatedContact = currentContact;
switch (lockResult.Status)
{
case LockAttemptStatus.Success:
var lockedContact = lockResult.Object;
lockedContact.ContactSaveMode = ContactSaveMode.AlwaysSave;
updatedContact = UpdateContactInfo(lockedContact, firstName, lastName, email, identifier); //this method should do work on lockedContact and return lockedContact. this then saves it as updatedContact.
break;
case LockAttemptStatus.NotFound:
updatedContact = currentContact;
break;
default:
var exception = new Exception(GetType() + " Contact could not be locked - " + currentContact.ContactId);
Sitecore.Diagnostics.Log.Error(string.Format("[{1}:SaveContactInfoToXdb] Can't Lock or Find xDB Contact {0}", currentContact.ContactId, GetType()), exception);
break;
}
//Save Contact Information - This is the ContactManager method to use
XdbContactManager.SaveAndReleaseContact(updatedContact);
}
catch (Exception exception)
{
Sitecore.Diagnostics.Log.Error($"Error in updating contact identifier {identifier}", exception);
}
}
private Contact UpdateContactInfo(Contact lockedContact, string firstName, string lastName, string customerId, string email)
{
try
{
var personalInfo = lockedContact.GetFacet<IContactPersonalInfo>("Personal");
personalInfo.FirstName = firstName;
personalInfo.Surname = lastName;
var contactEmails = lockedContact.GetFacet<IContactEmailAddresses>("Emails");
if (contactEmails != null)
{
IEmailAddress contactEmail = null;
if (contactEmails.Entries.Contains("Personal Email"))
{
contactEmail = contactEmails.Entries["Personal Email"];
}
else
{
contactEmail = contactEmails.Entries.Create("Personal Email");
contactEmails.Preferred = "Personal Email";
}
contactEmail.SmtpAddress = email;
}
}
catch (Exception exception)
{
Sitecore.Diagnostics.Log.Error($"Error in updating contact identifier {lockedContact.Identifiers.Identifier}", exception);
return null;
}
return lockedContact;
}
}
}
This can then be called wrapped in a Try-Catch to ensure no impacts to other functionality should something go wrong (Personalisation is a "Nice to have" not a "Must Have" right?) and run using something like this:
try
{
//fire update xDB event in the background but don't wait for it to complete and don't worry about if it succeeds or not (personalization is "nice to have" only).
Task.Run(() => _updateAnalyticsRepositoryClient.UpdateXdbContact(customerOrderModel.Customer.CustomerId, customerOrderModel.Customer.FirstName, customerOrderModel.Customer.LastName, customerOrderModel.Customer.Email));
}
catch (Exception ex)
{
_logManagerClient.Warn(string.Format("Error updating XDB data for customer {0}. Exception was: {1}", customerId, ex));
throw;
}
Excuse the PoC code but give that a go and see what you think :)