Create a calculated facet

Abstract

How to create a calculated xConnect facet.

This topic demonstrates how to implemented a calculated facet named AverageSpend. The facet stores data about the contact’s purchase history, including average spend per purchase.

Create and register a facet

  1. Create a facet class:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace Documentation
    {
        [FacetKey(MyCollectionModel.FacetKeys.AverageSpend)]
        public class AverageSpend : Sitecore.XConnect.Facet
        {
            public const string DefaultFacetKey = MyCollectionModel.FacetKeys.AverageSpend;
    
            public int TotalPurchases { get; set; }
            public decimal TotalSpend { get; set; } // Assumes single currency
            public decimal AverageSpendPerPurchase { get; set; }
        }
    }

    Note

    This example assumes that all purchases are made using the same currency.

  2. Register the facet in a model:

    modelBuilder.DefineFacet<Contact, AverageSpend>(AverageSpend.DefaultFacetKey);

Implement a calculated facet plugin

  1. Create a calculated facet handler that implements Sitecore.XConnect.Service.MergingCalculatedFacetHandler<AverageSpend>. Notice the following:

    • An InteractionFacetDependency object is passed into the base handler. This ensures that the WebVisit facet is loaded if it is present on the interaction. However, the facet is not required for the handler to run.

    • The Merge() method determines what happens to this facet when two contacts are merged. In this, the total spend and total purchase are added together, and the average spend per purchase is recalculated. You must implement the Merge() method for calculated facets.

    • The UpdateFacet() method determines what happens when a new interaction is submitted to xConnect.

    • All methods return true if the facet was updated.

    using Sitecore.XConnect;
    using Sitecore.XConnect.Collection.Model;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace Documentation
    {
        public class AverageSpendHandler : Sitecore.XConnect.Service.MergingCalculatedFacetHandler<AverageSpend>
        {
            public AverageSpendHandler() : base(AverageSpend.DefaultFacetKey, new[] { new Sitecore.XConnect.Service.InteractionFacetDependency(WebVisit.DefaultFacetKey, false) })
            {
            }
    
            protected override bool Merge(AverageSpend source, AverageSpend target)
            {
                if (source.TotalSpend > 0)
                {
                    target.TotalSpend += source.TotalSpend;
                    target.TotalPurchases += source.TotalPurchases;
    
                    target.AverageSpendPerPurchase = target.TotalSpend / target.TotalPurchases;
    
                    return true;
                }
    
                return false;
            }
    
            protected override bool UpdateFacet(AverageSpend currentFacet, Sitecore.XConnect.Interaction interaction)
            {
                bool edited = false;
    
                var referrer = interaction.WebVisit() != null ? interaction.WebVisit().Referrer : String.Empty;
    
                if (!String.IsNullOrEmpty(referrer))
                {
                    currentFacet.LatestReferrer = referrer;
                    edited = true;
                }
    
                var outcomes = interaction.Events.OfType<Outcome>(); // Get all purchase outcomes in interaction
    
                if (outcomes != null && outcomes.Any())
                {
                    foreach (var purchase in outcomes)
                    {
                        currentFacet.TotalPurchases++; // If there are purchases, increment purchase count
                        currentFacet.TotalSpend += purchase.MonetaryValue;
                    }
    
                    // Set average spend per purchase (NOT spend per visit)
                    currentFacet.AverageSpendPerPurchase = currentFacet.TotalSpend / currentFacet.TotalPurchases;
                }
    
                return edited;
            }
        }

    Note

    If the the Required property of any InteractionFacetDependency is set to true, the handler will only execute if this facet is present on the interaction.

  2. Open the \App_data\config\sitecore\Collection\sc.XConnect.Collection.Model.Plugins.xml configuration file in xConnect.

  3. Add two entries for the handler - one for ICalculatedFacetHandler and one for IContactMergeHandler:

    <ICalculatedFacetHandler.AverageSpendHandler>
        <Type>Documentation.AverageSpendHandler, Documentation</Type>
        <As>Sitecore.XConnect.Service.ICalculatedFacetHandler, Sitecore.XConnect.Service</As>
        <LifeTime>Singleton</LifeTime>
    </ICalculatedFacetHandler.AverageSpendHandler>
    
    <IContactMergeHandler.AverageSpendHandler>
        <Type>Documentation.AverageSpendHandler, Documentation</Type>
        <As>Sitecore.XConnect.Service.IContactMergeHandler, Sitecore.XConnect.Service</As>
        <LifeTime>Singleton</LifeTime>
    </IContactMergeHandler.AverageSpendHandler>

Pass in configuration options

You can pass configuration options into a calculated facet handler. In the following example, a configuration option controls whether or not the LatestReferrer property of the AverageSpend facet is set.

  1. Add a public property to represent your option - for example, public bool PopulateReferrer { get; }.

  2. Add a constructor that takes an IConfiguration object. The following scenario assumes that the configuration option is named PopulateReferrer:

    using Microsoft.Extensions.Configuration;
    using Sitecore.XConnect;
    using Sitecore.XConnect.Collection.Model;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace Documentation
    {
        public class AverageSpendHandler : Sitecore.XConnect.Service.MergingCalculatedFacetHandler<AverageSpend>
        {
    
            public AverageSpendHandler(IConfiguration options) : this(options.GetValue<bool>(nameof(PopulateReferrer)))
            {
            }
    
            public AverageSpendHandler(bool populateReferrer) : base(AverageSpend.DefaultFacetKey, new[] { new Sitecore.XConnect.Service.InteractionFacetDependency(WebVisit.DefaultFacetKey, false) })
            {
                PopulateReferrer = populateReferrer;
            }
    
            public bool PopulateReferrer { get; }
    
            protected override bool Merge(AverageSpend source, AverageSpend target)
            {
                bool edited = false;
    
                if (PopulateReferrer)
                {
                    if (String.IsNullOrEmpty(target.LatestReferrer))
                    {
                        target.LatestReferrer = source.LatestReferrer;
                        edited = true;
                    }
                }
    
                if (source.TotalSpend > 0)
                {
                    target.TotalSpend += source.TotalSpend;
                    target.TotalPurchases += source.TotalPurchases;
    
                    target.AverageSpendPerPurchase = target.TotalSpend / target.TotalPurchases;
    
                    return edited;
                }
    
                return false;
            }
    
            protected override bool UpdateFacet(AverageSpend currentFacet, Sitecore.XConnect.Interaction interaction)
            {
                bool edited = false;
    
                var referrer = interaction.WebVisit() != null ? interaction.WebVisit().Referrer : String.Empty;
    
                if (PopulateReferrer && !String.IsNullOrEmpty(referrer))
                {
                    currentFacet.LatestReferrer = referrer;
                    edited = true;
                }
    
                var outcomes = interaction.Events.OfType<Outcome>(); // Get all purchase outcomes in interaction
    
                if (outcomes != null && outcomes.Any())
                {
                    foreach (var purchase in outcomes)
                    {
                        currentFacet.TotalPurchases++; // If there are purchases, increment purchase count
                        currentFacet.TotalSpend += purchase.MonetaryValue;
                    }
    
                    // Set average spend per purchase (NOT spend per visit)
                    currentFacet.AverageSpendPerPurchase = currentFacet.TotalSpend / currentFacet.TotalPurchases;
                }
    
                return edited;
            }
        }
    }
  3. Open \App_data\config\sitecore\Collection\sc.XConnect.Collection.Model.Plugins.xml and add options into configuration as shown. If the option is used during merge and update, specify the option twice as shown.

    <ICalculatedFacetHandler.AverageSpendHandler>
        <Type>Documentation.AverageSpendHandler, Documentation</Type>
        <As>Sitecore.XConnect.Service.ICalculatedFacetHandler, Sitecore.XConnect.Service</As>
        <LifeTime>Singleton</LifeTime>
        <Options>
            <PopulateReferrer>true</PopulateReferrer>
        </Options>
    </ICalculatedFacetHandler.AverageSpendHandler>
    
    <IContactMergeHandler.AverageSpendHandler>
        <Type>Documentation.AverageSpendHandler, Documentation</Type>
        <As>Sitecore.XConnect.Service.IContactMergeHandler, Sitecore.XConnect.Service</As>
        <LifeTime>Singleton</LifeTime>
        <Options>
            <PopulateReferrer>true</PopulateReferrer>
        </Options>
    </IContactMergeHandler.AverageSpendHandler>

Get calculated facets

Use the xConnect Client API to retrieve calculated facets in the same way as any other facet. Do not edit calculated facets directly.