In Sitecore when creating your own contacts you can get the following exception:
10604 10:16:27 ERROR General error when submitting contact. Exception: System.InvalidOperationException Message: Another contact with the same identifier already exists. Source: Sitecore.Analytics.MongoDB at Sitecore.Analytics.Data.DataAccess.MongoDb.MongoDbDataAdapterProvider.SaveContactWithIdentifier(IContact contact, ContactSaveOptions saveOptions) at Sitecore.Analytics.Data.DataAccess.MongoDb.MongoDbDataAdapterProvider.<>c__DisplayClass9.<SaveContact>b__7() at Sitecore.Analytics.Data.DataAccess.MongoDb.MongoDbDataAdapterProvider.Try(Action action) at Sitecore.Analytics.Data.DataAccess.MongoDb.MongoDbDataAdapterProvider.SaveContact(IContact contact, ContactSaveOptions saveOptions) at Sitecore.Analytics.Data.ContactRepository.SaveContact(Contact contact, ContactSaveOptions options) at Sitecore.Analytics.Tracking.ContactManager.SubmitContact(Contact contact, ContactSaveOptions options, Contact& realContact)
The error can occur if you try to flush a contact with a new contact ID but with an existing identifier. It usually is a result of Sitecore having a mismatch between what the ContactRepository and the MongoDB contains.
This pseudocode explains the situation:
ContactRepository contactRepository = Factory.CreateObject("tracking/contactRepository", true) as ContactRepository; ContactManager contactManager = Factory.CreateObject("tracking/contactManager", true) as ContactManager; // Contact is loaded from repository but repository cannot find it Contact contact = contactRepository.LoadContactReadOnly(username); // ... so we try to create it if (contact == null) { // Contact is created in memory contact = contactRepository.CreateContact(ID.NewID); contact.Identifiers.Identifier = username; // And we try to write it to xDB (MongoDB) // but the contact is already there. contactManager.FlushContactToXdb(contact); }
The problem arises as the FlushContactToXdb does not throw an exception, so you are left with the impression that the Contact is created and everything is fine.
If you are using the ExtendedContactRepository as I described in the blog post Sitecore Contacts – Create and save contacts to and from xDB (MongoDB), you risk that the GetOrCreateContact method goes in an infinite loop because:
- We try to load the contact, but the contact is not found
- We Create a new contact
- We flush the contact, but the flush method fails silently
- We call step 1
To avoid the infinite loop, please see Dan’s suggestion to how to solve it:
Or you can add a retry counter in the class.
Add the following 2 lines to the class:
private const int _MAX_RETRIES = 10; private int _retries;
Then modify the private method GetOrCreateContact:
private Contact GetOrCreateContact(string username, LockAttemptResult<Contact> lockAttempt, ContactRepository contactRepository, ContactManager contactManager) { switch (lockAttempt.Status) { case LockAttemptStatus.Success: Contact lockedContact = lockAttempt.Object; lockedContact.ContactSaveMode = ContactSaveMode.AlwaysSave; return lockedContact; case LockAttemptStatus.NotFound: Contact createdContact = CreateContact(username, contactRepository); Log.Info(string.Format("{0}: Flushing contact '{1}' (contact ID '{2}') to xDB.", GetType().Name, createdContact.Identifiers.Identifier, createdContact.ContactId), this); contactManager.FlushContactToXdb(createdContact); // NEW CODE: Check for retries, and throw an exception if retry count is reached _retries++; if (_retries >= _MAX_RETRIES) throw new Exception(string.Format("ExtendedContactRepository: Contact {0} could not be created. ", username)); // END NEW CODE return GetOrCreateContact(username); default: throw new Exception("ExtendedContactRepository: Contact could not be locked - " + username); } }
This is still experimental code, but as described before, so is the ExtendedContactRepository.
MORE TO READ:
