2

We have a Sitecore (8.2) site that is already using custom facets to extend the contact information stored in xDB. A new requirement came along, and we would like to extend one of the facets. Seemed easy - we add an extra property to the interface that was configured in the <model> definition. The code was fine, no errors.. but in the MongoDb we can see that for new contacts the data is saved, but for existing contacts (no merge, just through their cookies) the new data is not added - other data is updated though, so save is working.

We might have other options: create a new extra facet (if that would help), use tags, ... but it actually sounds logical to add the data to the existing facet.

So my question is simple: is it possible to add an extra property to an existing facet (once you have existing contacts)? And if yes, what might I be doing wrong?

Code:

public interface IVisitorFacet : IFacet
{
    string Company { get; set; }
    string Phone { get; set; }
    ...  // new property will come here
}

Config:

<model>
  <elements>
    <element interface="...IVisitorFacet, X" implementation="....VisitorFacet, X" />
  </elements>
  <entities>
    <contact>
      <facets>
        <facet name="Custom Data" contract="....IVisitorFacet, X" />
      </facets>
    </contact>
  </entities>
</model>

So we have a facet with 2 properties - working perfectly. When we add a 3rd, it is only written to Mongo for new contacts.

  • How about the original fields of that facet? Are they updated normally? Also, are you on 8.2 Initial Release? – Dmytro Shevchenko Feb 13 '17 at 20:59
  • I have follow up questions to assist with my answer: Can you provide a code snippet of h ow you added the extra interface to the <model> definition? Are you adding to an OOTB Facet? Or did you create your own facet? – Pete Navarra Feb 13 '17 at 23:10
  • It's a custom facet that has been working for over a year. We want to add an extra field to the facet now.. older fields are still updated as expected. 8.2 initial indeed. Will add some code and config – Gatogordo Feb 14 '17 at 8:16
  • And in your concrete class you are ensuring a value in constructor? – Pete Navarra Feb 14 '17 at 12:39
  • That should work just fine for all contacts. I would then maybe ask to see some code forvwhen writing a value to the new attribute. – Pete Navarra Feb 14 '17 at 12:41
4

When A Custom Facet Is Extended, Mongo Is Not Immediately Affected

Just because Mongo isn't showing the added facet/element/attribute, doesn't mean that the Facet isn't there. MongoDB will only store Facet information if there is information to store. By adding an element to the Contact Facet model, that does not automatically update MongoDB directly.

The only way for MongoDB to update directly is to update the contact record, save a value to the new facet element/attribute, and then flush to xDB or release the contact.

A Lot of Background Information

Extending Existing OOTB Facets

You can not extend the Existing OOTB Facets because the Concrete Classes that represent them are protected (internal). You would have to recreate the concrete class from scratch.

using Sitecore.Analytics.Model.Entities;
using Sitecore.Analytics.Model.Framework;
using System;

namespace Sitecore.Analytics.Model.Generated
{
  [Serializable]
  internal class ContactEmailAddresses : Facet, IContactEmailAddresses, IFacet, IElement, IValidatable
  {
    private const string PREFERRED = "Preferred";
    private const string ENTRIES = "Entries";

    public IElementDictionary<IEmailAddress> Entries
    {
      get
      {
        return this.GetDictionary<IEmailAddress>("Entries");
      }
    }

    public string Preferred
    {
      get
      {
        return this.GetAttribute<string>("Preferred");
      }
      set
      {
        this.SetAttribute<string>("Preferred", value);
      }
    }

    public ContactEmailAddresses()
    {
      this.EnsureAttribute<string>("Preferred");
      this.EnsureDictionary<IEmailAddress>("Entries");
    }
  }
}

To achieve this instead, you'd want to make a whole new facet model. To validate how you have created your custom contact facet, see below for an example on how I do it.

Creating Custom Facet's

In the example below, I've called that Marketing Data.

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
    <sitecore>
    <model>
      <elements>
        <element interface="SitecoreHacker.Sandbox.Facets.IMarketingData, SitecoreHacker.Sandbox"
                 implementation="SitecoreHacker.Sandbox.Facets.MarketingData, SitecoreHacker.Sandbox"/>
      </elements>
      <entities>
        <contact>
          <facets>
            <facet name="Marketing Data" contract="SitecoreHacker.Sandbox.Facets.IMarketingData, SitecoreHacker.Sandbox"/>
          </facets>
        </contact>
      </entities>
    </model>    
  </sitecore>
</configuration>

Step 1: Gather information about the facet

Create a custom facet is actually not that difficult. What’s more difficult, IMHO, is obtaining what kind of information you want in your facet? Getting the following stuff together before you start coding will allow you not break your coding groove.

  • The name of the Facet.
  • What information do you want to capture?
  • Name of field
  • Type of field (strings, GUID, integrers, and floats.

Step 2: Create the element contract that defines the facet

By creating a facet, what we’re really doing is creating the root element interface that will contain all of the other structures going into our facet. We will define our interface inheriting from IFacet.

using Sitecore.Analytics.Model.Framework;

namespace SitecoreHacker.Sandbox.Facets
{
  public interface IMarketingData : IFacet
  {
    string CustomerId { get; set; }
    string Segment { get; set; }
   }
}

Here I have defined an interface called IMarketingData that is based on the IFacet interface. There are two things I want to capture in this IFacet: CustomerId and Segment. Both of these values are strings.

That was easy, right?

Step 3: Create the implementation of your element contract

Now I want to create the concrete class that can implement the interface that we just created.

using System;
using Sitecore.Analytics.Model.Framework;

namespace SitecoreHacker.Sandbox.Facets
{
    [Serializable]
    public class MarketingData: Facet, IMarketingData
    {
      private const string CUSTOMER_ID = "CustomerId";
      private const string SEGEMENT = "Segment";

      #region Properties
      public string CustomerId
      {
          get { return GetAttribute<string>(CUSTOMER_ID); }
          set { SetAttribute(CUSTOMER_ID, value); }
      }

      public string Segment
      {
          get { return GetAttribute<string>(SEGEMENT); }
          set { SetAttribute(SEGEMENT, value); }
      }
      #endregion

      public MarketingData()
      {
          EnsureAttribute<string>(CUSTOMER_ID);
          EnsureAttribute<string>(SEGEMENT);
      }
  }
}

Alright, there’s a little bit more going on here so let’s step through it.

First, just create a class and base it off of Facet and IMarketingData We base off of Facet because this is our root Element. If we were just creating an Element that would be used in this Facet, and in addition to the Facet Element, then we would have a base class of Element More on that later. Add the [Serializable] attribute.

This would seem unimportant, but becomes super important depending on the type of Session State being run. So just use it. Don’t ask questions. =) Now the implementation class has three parts as follows.

Don’t implement any other functions in this class outside of the following:

  • Constants
    • For every attribute that you have, define a constant that will provide a label for that attribute.
    • Protip: I found out the hard way that the constants defined, the value must equal the actual name of the class property for the same attribute.
  • Class Properties
    • For every attribute, create getters and setters.
    • Important: Name the Property the same as the value of the constant that you set for it.
  • For Getters:
    • return GetAttribute<type>(string name);
    • Type of your attribute: string, Guid, Integers, or Float name: Reference the constant variable defined for your attribute.
  • For Setters
    • SetAttribute<type>(string name, value);
    • Type of your attribute: string, Guid, Integers, or Float name: Reference the constant variable defined for your attribute.
  • Class Constructor
    • No parameters
    • Call EnsureAttribute<string>(string name); for each attribute.

Save and compile! Now, you'll want to run something that saves information to that Facet. As long as there is actual data to save to the Facet, Mongo will store it.

Mongo Marketing Data Facet

Extending the Custom Contact Facet Data Model

Step 1: Create The Element Contract

We start off by creating an interface for our new Element. Using this example, we create the “IProduct” interface with two property attributes: Product Name, and Product Price.

If you recall from ealier, Elements plug into the Facet, and a Facet is and of itself an Element as well as a collection of Elements.

using Sitecore.Analytics.Model.Framework;

namespace SitecoreHacker.Sandbox.Facets
{
    public interface IProduct : IElement
    {
        string ProductName { get; set; }
        string ProductPrice { get; set; }
    }
}

Simple interface deriving from the IElement interface. Now we need to create our concrete class that will support this interface. We will call this the “Product” class.

Step 2: Create The Element Concrete Class

using System;
using Sitecore.Analytics.Model.Framework;

namespace SitecoreHacker.Sandbox.Facets
{
    [Serializable]
    public class Product : Element, IProduct
    {
        private const string PRODUCT_NAME = "ProductName";
        private const string PRODUCT_PRICE = "ProductPrice";

        public string ProductName
        {
            get { return GetAttribute<string>(PRODUCT_NAME); }
            set { SetAttribute(PRODUCT_NAME, value); }
        }
        public string ProductPrice
        {
            get { return GetAttribute<string>(PRODUCT_PRICE); }
            set { SetAttribute(PRODUCT_PRICE, value); }
        }

        public Product()
        {
            EnsureAttribute<string>(PRODUCT_NAME);
            EnsureAttribute<string>(PRODUCT_PRICE);
        }
    }
}

You’ll note that this is a pretty straight forward class.

Here we have setup the ProductName and ProductPrice property elements for a single Product Element. This is the single object representation for the collection objects that our Facet (“Marketing Data”) is going to reference.

Step 3: Add Element Dictionary to our Parent Element Interface

In this case, our parent element is our class based off of the IFacet contract. I call this out because you can nest elements within elements, but that’s outside the scope of this blog. We’ll reopen our IMarketingData contract and add the Element Dictionary Property.

using Sitecore.Analytics.Model.Framework;

namespace SitecoreHacker.Sandbox.Facets
{
    public interface IMarketingData : IFacet
    {
        string CustomerId { get; set; }
        string Segment { get; set; }

        IElementDictionary<IProduct> Products { get; }
    }
}

Note here that IElementDictionary Products {get;} is the property attribute that we added to the IMarketingDara Facet. IElementDictionary is the class that Sitecore uses to refer to a collection of like Elements. In this case, our like Element is the IProduct type.

Step 4: Add Element Dictionary to our Parent Element Class

We’ve finished updating our interface, now we need to update the concrete class representing our parent element/facet and implement the IElementDictionary property.

using System;
using Sitecore.Analytics.Model.Framework;

namespace SitecoreHacker.Sandbox.Facets
{
    [Serializable]
    public class MarketingData: Facet, IMarketingData
    {

        private const string CUSTOMER_ID = "CustomerId";
        private const string SEGEMENT = "Segment";
        private const string PRODUCTS = "Products";

        #region Properties
        public string CustomerId
        {
            get { return GetAttribute<string>(CUSTOMER_ID); }
            set { SetAttribute(CUSTOMER_ID, value); }
        }

        public string Segment
        {
            get { return GetAttribute<string>(SEGEMENT); }
            set { SetAttribute(SEGEMENT, value); }
        }

        public IElementDictionary<IProduct> Products
        {
            get { return GetDictionary<IProduct>(PRODUCTS); }
        }
        #endregion

        public MarketingData()
        {
            EnsureAttribute<string>(CUSTOMER_ID);
            EnsureAttribute<string>(SEGEMENT);
            EnsureDictionary<IProduct>(PRODUCTS);
        }
    }
}

Stepping through this we do the same three steps for every property in an element:

  • We’ve added our constant string variable which contacts the text name of our dictionary property.
  • Implemented the Property based on the contract.
  • In the constructor, we’ve Ensured that the Dictionary is loaded.

Step 5: Patch New Element to Model Configuration

Finally, we just need to add our Element to the Element collection for the Contact model in the Sitecore configuration.

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <model>
      <elements>
        <element interface="SitecoreHacker.Sandbox.Facets.IMarketingData, SitecoreHacker.Sandbox"
                 implementation="SitecoreHacker.Sandbox.Facets.MarketingData, SitecoreHacker.Sandbox"/>
        <element interface="SitecoreHacker.Sandbox.Facets.IProduct, SitecoreHacker.Sandbox"
                 implementation="SitecoreHacker.Sandbox.Facets.Product, SitecoreHacker.Sandbox"/>
      </elements>
      <entities>
        <contact>
          <facets>
            <facet name="Marketing Data" contract="SitecoreHacker.Sandbox.Facets.IMarketingData, SitecoreHacker.Sandbox"/>
          </facets>
        </contact>
      </entities>
    </model>    
  </sitecore>
</configuration>

We have now extended our already custom Facet, and added an additional Element collection (ElementDictionary) to hold additional information.

Now you'll want to make sure that you save a contact with information for the extended facet. If you've followed these instructions, then Mongo will show you something similar to the below contact: Mongo With Extended Custom Contact Facet

Additional Information About Uploading Contacts with Custom Facets

Depending on how you create new Contacts... could have an effect on existing contact facet data. If you use the CSV import, you should be aware that as of 8.2 Update 2, there is still a bug in Sitecore's List Manager that will remove custom facet information for an existing contact, if said contact Identifier is uploaded via CSV. I don't think that is your problem, but if it is, or you have the scenario, let me know, and I'll provide additional information.

  • 1
    Thx for the massive answer.. and somewhere in there was indeed the fix. Somehow we did forget to add the EnsureAttribute in the constructor and were blinded as it did seem to work for new contacts. Weird, but fixed now. – Gatogordo Feb 14 '17 at 15:43

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.