9

I am having this weird issue with lockResult.object carrying null/empty value for the "identifier" property. Though the lockedResult returns with "success" status and the contact has the identifier value.

var currentContact = GetXdbContact(Email);
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 = UpdateContact(lockedContact);  //my custom method
        break;
}

private Contact GetXdbContact(string Email)
{
    var contact = _repository.LoadContactReadOnly(Email);

    if (contact != null) return contact;

    //If null, Identify the current session contact
    contact = Tracker.Current.Session.Contact;
    Tracker.Current.Session.Identify(Email);

    if (contact == null) return null;

    contact.Identifiers.AuthenticationLevel = AuthenticationLevel.None;
    contact.System.Classification = 0;
    contact.ContactSaveMode = ContactSaveMode.AlwaysSave;
    contact.Identifiers.IdentificationLevel = ContactIdentificationLevel.Known;
    contact.Identifiers.Identifier = Email;
    contact.System.OverrideClassification = 0;
    contact.System.Value = 0;
    contact.System.VisitCount = 0;
    return contact;
}

Also, I noticed this is only happening every time I deploy binaries to website root and try to create a contact. Subsequent contacts creation is not having this issue.

Any pointers on why the identifier property is empty?

7
  • When you say the "identifier" is null, do you mean lockedContact.ContactId? Or lockedContact.Identifiers.Identifier? Commented Dec 16, 2016 at 14:02
  • Also, your code does not show anything related to contact creation that you mention in the question. Could you clarify how and when the creation of new contacts comes into play? Preferably, post all the relevant code that loads, creates and saves contacts. Commented Dec 16, 2016 at 14:04
  • I mean lockedContact.Identifiers is null.
    – Mohit
    Commented Dec 16, 2016 at 14:29
  • updated the post with code
    – Mohit
    Commented Dec 16, 2016 at 14:35
  • This only brings more questions. 1) I still don't see where you are creating new contacts. 2) What are you attempting to do with this code? 3) Why are you assigning standard fields of the contact, such as contact.System.Classification? 4) When you already have a contact object in currentContact, why are you trying to obtain another contact object from XdbContactManager.TryLoadContact? This doesn't make sense to me... Please answer all of the above questions. Commented Dec 16, 2016 at 14:45

1 Answer 1

3

Analysis and explanation

There are several scenarios that may occur in the code you have posted. Below, I will only analyze the scenario that will lead to the problem you are experiencing.

  1. Contact A is found in the Collection database by its email address:

    var contact = _repository.LoadContactReadOnly(Email);
    
  2. It is returned from the method GetXdbContact and saved to the variable currentContact.

  3. Contact B is loaded by its ID. It is then available as lockResult.Object.

    lockResult = XdbContactManager.TryLoadContact(currentContact.ContactId);
    

As you can see, it is very possible that you can load two different contacts: one by its Email, the other by its ID. While contact A is guaranteed to have an identifier (that's how you found it), contact B may well be unidentified. Obviously, the two contacts in this scenario will be completely unrelated to each other.

Suggested solution

From your code, I can deduce that you are trying to update some contact fields without knowing whether the contact currently has a live session. I propose the following approach to this.

First, create a method that updates a given contact's fields, and does nothing else. We'll use this method to work with contacts that come from the session, from the tracker, or from the database.

public void UpdateContactData(Contact contact)
{
    // set contact fields, facets, etc.
}

Then, in a more or less reliable way, you can use the following method to find the contact by its identifier, update its fields, and put it back to whatever storage we found it in:

void UpdateContact(string identifier, Action<Contact> updateMethod)
{
    var contactRepository = (ContactRepositoryBase)Factory.CreateObject("contactRepository", true);
    var sharedSessionManager = (SharedSessionStateManager)Factory.CreateObject("tracking/sharedSessionState/manager", true)

    if (Tracker.Current.Contact != null && Tracker.Current.Contact.Identifiers.Identifier == identifier)
    {
        // The current contact is the one we need to update.
        updateMethod(Tracker.Current.Contact);
    }
    else
    {
        // Find out if a contact with the given identifier exists in the database.
        Contact databaseContact = contactRepository.LoadContactReadOnly(identifier);

        if (databaseContact != null)
        {
            Guid contactId = databaseContact.ContactId;

            // Try to load the contact from the Shared Session.
            Contact sessionContact = sharedSessionManager.LockAndLoadContact(contactId);

            if (sessionContact != null)
            {
                // Update this contact and release it back to the Shared Session.
                updateMethod(sessionContact);

                sharedSessionManager.SaveAndReleaseContact(sessionContact);
            }
            else
            {
                // In this branch, we know that the contact we need exists in the database, but it's not locked by our cluster.
                // We'll try to lock it and update its data.
                LeaseOwner leaseOwner = new LeaseOwner("SOME_UNIQUE_WORKER_NAME", LeaseOwnerType.OutOfRequestWorker);
                LockAttemptResult<Contact> lockResult = contactRepository.TryLoadContact(identifier, leaseOwner, TimeSpan.FromMinutes(1));

                if (lockResult.Status == LockAttemptStatus.Success)
                {
                    // Update the contact and release it to the Collection DB.
                    updateMethod(lockResult.Object);

                    var options = new ContactSaveOptions(release: true, owner: leaseOwner);
                    contactRepository.SaveContact(lockResult.Object, options);
                }
                else
                {
                    // Log the inability to lock the contact.
                    // Decide what else you want to do in that case.
                }
            }
        }
        else
        {
            // In this branch, we know that the contact with the provided identifier does not exist yet.
            // You will have to decide what to do in that case. Below is an example where you identify the current contact.

            Tracker.Current.Session.Identify(identifier);
            updateMethod(Tracker.Current.Contact);
        }
    }
}

Here's how you can use this method:

string identifier = "[email protected]";

UpdateContact(identifier, UpdateContactData);
2
  • Thank you so much for your detailed analysis and solution. Appreciate your help here. Couple of questions: I also want to Flush the contact after the Facets are created or updated. Can I use _contactManager.FlushContacttoXdb(contact) just after sharedSessionManager.SaveAndReleaseContact(sessionContact); and XdbContactRepository.SaveContact(lockResult.Object, options) ? From the code you posted above, the first if condition calls the updateMethod without obtaining a lock or call SaveContact? Does it mean the current contact already has the lock?
    – Mohit
    Commented Dec 18, 2016 at 22:29
  • 1) Yes, you can flush. It only makes sense to flush after you save it in the session though. When you save it via the repository, it's saved directly to the database, so flushing would be superfluous. 2) The current contact is managed by Sitecore, and it makes sure that it is properly locked, yes. Commented Dec 19, 2016 at 6:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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