11

What I am trying to do is:

  • get or create a contact in xDB by identifier
  • update its facets
  • save the contact to xdb (not to session)

I am running Sitecore 8.2 and I am using this extended contact repository https://briancaos.wordpress.com/2015/10/09/sitecore-contacts-create-and-save-contacts-directly-to-and-from-xdb-mongodb/

Trying to debug.. after this code

Contact createdContact = CreateContact(userName, contactRepository);
contactManager.FlushContactToXdb(createdContact);

when calling

Contact contact = contactRepository.LoadContactReadOnly(userName);

the contact is always null. Also nothing flushes to mongoDB.

methods contactManager.FlushToXdb(contact) or contactManager.SaveAndReleaseContactToXdb(contact) simply don't flush anything.

What can be wrong? I can confirm that this code worked fine on Sitecore 8.1 update 2

  • I would like to suggest re-wording your question title. It clear based on your details, your question is "How to create or update a contact in xdb". "How to create or update a list of contacts in xdb" is in fact something completely different (imho). – Pete Navarra Dec 2 '16 at 1:06
7

I don't see a reason to use the Contact Manager—it works with the Shared Session and only then with the Collection Database. Just using the Contact Repository should be enough in your case, as it reads and writes data directly from the Collection DB.

public void SetContactData(string username)
{
    LeaseOwner leaseOwner = new LeaseOwner("YOUR_WORKER_NAME", LeaseOwnerType.OutOfRequestWorker);

    ContactRepositoryBase contactRepository = Factory.CreateObject("contactRepository", true) as ContactRepositoryBase;

    // Attempt to obtain an exclusive lock on an existing contact in xDB.
    LockAttemptResult<Contact> lockResult = contactRepository.TryLoadContact(username, leaseOwner, TimeSpan.FromMinutes(1));

    Contact contact = null;

    if (lockResult.Status == LockAttemptStatus.AlreadyLocked)
    {
        // Another worker or a live web session has an exclusive lock on the contact.
        // You can't use this contact right now. It's up to you what to do in this case.
        /* ... */
    }
    else if (lockResult.Status == LockAttemptStatus.DatabaseUnavailable)
    {
        // Database is down. Try to handle this gracefully.
        /* ... */
    }
    else if (lockResult.Status == LockAttemptStatus.NotFound)
    {
        // A contact with the given identifier doesn't exist.
        // Just create a new contact object.
        contact = contactRepository.CreateContact(Guid.NewGuid());

        // Identify it.
        contact.Identifiers.Identifier = username;

        // And make it known.
        contact.Identifiers.IdentificationLevel = Sitecore.Analytics.Model.ContactIdentificationLevel.Known;
    }
    else
    {
        // We successfull locked an existing contact.
        contact = lockResult.Object;
    }

    // Set some contact facets:
    /* ... */

    // Save the contact and release the lock.
    if (contact != null)
    {
        var options = new ContactSaveOptions(release: true, owner: leaseOwner);
        contactRepository.SaveContact(contact, options);
    }
}

Note that there are some edge cases that you should decide yourself how to handle. I left comments in the code above.

6

Immediate Answer

While I have never used the Extended Contact Repository you linked, I have it on good authority that the methods FlushToXdb() and SaveAndReleaseContacttoXdb() are not the methods you want to use in Sitecore 8.2.

I believe the method you want to call is ContactManager.SaveAndReleaseContact(updatedContact);

Background Info on modifying and creating Contacts

Here is my full example, which works, again, not using the Extended Contact Repository:

Referencing Repository and Manager

This is used in the below examples where XdbContactRepository and XdbContactManager are used.

For full explanation, I've created a Factory for Contacts, that has an XdbContactFactory, so that in the event Sitecore changes up contacts again, I can just create a new factory and it'll work. I didn't expose the whole factory below. Just the relevant pieces.

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

Creating/Loading the Contact

    public override 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;
    }

Modifing the Contact

        //Above Method
        var currentContact = GetXdbContact(contactIfo.EmailAddress);

        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 = SomeCustomMethod(lockedContact);  //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);
                Logger.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);

Contact Factets

I have found it WAY easier to create extension methods for accessing Contact facets.

    public static IEmailAddress GetPreferredEmailAddress(this Contact contact)
    {
        var emailFacet = contact.GetFacet<IContactEmailAddresses>("Emails");
        if (emailFacet.IsEmpty) return null;

        var preferredEmail = emailFacet.Entries[emailFacet.Preferred];
        return preferredEmail.IsEmpty ? null : preferredEmail;
    }

    public static IMarketingData GetMarketingData(this Contact contact)
    {
        // THIS IS A CUSTOM FACET THAT I CREATED FOR DEMO PURPOSE
        var marketingFacet = contact.GetFacet<IMarketingData>("Marketing Data");
        return marketingFacet;
    }

    public static IContactPersonalInfo GetPersonalProfile(this Contact contact)
    {
        var facet = contact.GetFacet<IContactPersonalInfo>("Personal");
        return facet;
    }

    public static IContactAddresses GetAddressProfile(this Contact contact)
    {
        var facet = contact.GetFacet<IContactAddresses>("Addresses");
        return facet;
    }

    public static IContactEmailAddresses GetEmailsProfile(this Contact contact)
    {
        var facet = contact.GetFacet<IContactEmailAddresses>("Emails");
        return facet;
    }

    public static IContactPhoneNumbers GetPhoneProfile(this Contact contact)
    {
        var facet = contact.GetFacet<IContactPhoneNumbers>("Phone Numbers");
        return facet;
    }
  • 1
    Thank you guys for help, your code works. But my issue was different. I added a facet with name containing dots, like <facet name="my.facet.name" type="...." /> and that has broken a great part of xdb. contacts were not creating, errors in the log around tracking.. But that was only facet name. crazy! – Volodymyr Hil Dec 2 '16 at 10:03
  • Yeah, that makes sense. The Facet Name is used as the element name in MongoDB to denote the beginning of the facet. Because the document is JSON, Mongo uses dot notation to specify a specific child element of the document. So, calling you facet "my.facet.name" to mongo makes it think you're trying to set { "my": { "facet" : { "name": { object}}}} (hint: this blows up the contact model). I do question that this worked in 8.1U2 because of this. I'm assuming the custom facet was added after the fact. – Pete Navarra Dec 2 '16 at 14:21
  • Pete - I think there may be a coding error in your answer. In your code for Modifying the Contact you create an object called "lockedContact" but you never really do anything with it. I believe that lockedContact is the object that really needs to be saved, not updatedContact. – Corey Burnett Dec 8 '16 at 14:46
  • Updated example to show use of lockedContact. – Pete Navarra Dec 9 '16 at 0:41
  • 2
    @PeteNavarra Please note that SaveAndReleaseContact will only save the contact to Shared Session. The contact will not be saved and unlocked in the database until the Shared Session expires. If you want to release it in the Collection DB immediately, the correct method to use is SaveAndReleaseContactToXdb(contactId). Please see my answer here for more details: sitecore.stackexchange.com/a/3237/104 — this stands for Sitecore 8.2 as well. – Dmytro Shevchenko Dec 9 '16 at 11:57

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.