---
title: "Create a calculated facet"
slug: "create-a-calculated-facet"
abstract: "How to create a calculated xConnect facet."
---
# Create a calculated 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:

    ```csharp
    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](https://doc.sitecore.com/xp/en/developers/103/sitecore-experience-platform/create-a-custom-model.html "Create a custom model") in a model:

    ```text
    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.

    ```csharp
    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`:

    ```xml
    <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`:

    ```csharp
    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.

    ```xml
    <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.