---
title: "Walkthrough: Reporting on a new dimension"
slug: "walkthrough--reporting-on-a-new-dimension"
abstract: "Describes how you create and use a new dimension. This is sometimes also called a flexible dimension."
---
# Walkthrough: Reporting on a new dimension

Sitecore has a number of dimensions that it reports on. If you want to report on a dimension that Sitecore does not report on, you can create your own dimension and collect metrics for it.

> [!note]
> This topic describes how you implement dimensions, also known as *flexible dimensions*, in Sitecore 9 and later.

This walkthrough describes how to:

- Create classes for dimensions and metrics - you create classes to represent the dimension and do the metrics calculations.
- Update the reporting database - you update the reporting database to store your dimension data there.
- Create items for dimension and metrics - you create Sitecore items that represent the dimension and its metrics.
- Configure the new dimension - you configure the dimension to make Sitecore use it.

When you have created and configured the new dimension, you can [add charts that display the data](https://doc.sitecore.com/xp/en/developers/104/sitecore-experience-manager/walkthrough--adding-a-bar-chart-to-an-experience-analytics-report.html).

<details>
<summary><h2>Create classes for dimensions and metrics</h2></summary>

The following procedures show how you create the classes where you implement your new dimensions and calculate metrics.

### Add references

Add references to these libraries in your Visual Studio solution:

```text
Sitecore.Analytics.Aggregation
Sitecore.ExperienceAnalytics.Aggregation
Sitecore.XConnect
Sitecore.Kernel
Sitecore.XConnect.Collection.Model
```

### Define a model

To create a model for the dimension and the metrics:

1. Create a class that extends the `DictionaryValue` class and the `IMergeableMetric<TableName>` interface. The naming convention for these classes is *<YourDimension>Metrics*, for example `PostCodeMetrics`, `OutcomeMetrics`, and so on.
2. Define properties for your metrics, for example `Visits`, `MonetaryValue`, and so on.
3. Implement the `MergeWith` method. In this method you specify how data that is calculated for a new interaction is merged into the already existing calculated data.

The following example shows the implementation of a dimension called `Browser` that collects engagement values and bounces of a visit:

```csharp
class BrowserMetrics : DictionaryValue, IMergeableMetric<BrowserMetrics>
    {
        public int Visits { get; set; }
        public int EngagementValue { get; set; }
        public int Bounces { get; set; }

        public T MergeWith<T>(T other) where T : BrowserMetrics, new()
        {
            if (other == null)
            {
                throw new ArgumentNullException(nameof(other));
            }

            return new T
            {
                Visits = Visits + other.Visits,
                Bounces = Bounces + other.Bounces,
                EngagementValue = other.EngagementValue
            };
        }
    }
```
> [!note]
> You use the names of the class and the metrics properties in a later task where you define the database schema.

### Create a group resolver

You must define a strategy for resolving the keys used to group the dimensions, for example *URL*, *Goal*, or *Outcome*. You do this by implementing a group resolver.

To create a group resolver:

1. Create a new class that extends the `IGroupResolver<T>` interface, where `T` is the type object you want to use as a key when you group records for your dimension.
2. Implement the `MeasureGroupOccurrences` method.

In the following example you measure metrics for *Browser* and, therefore, you use the `BrowserData` type to resolve groups:

```csharp
class BrowserResolver : IGroupResolver<BrowserData>
    {
        public IEnumerable<VisitGroupMeasurement<BrowserData>> MeasureGroupOccurrences(IInteractionAggregationContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            var browserData = context.Interaction.WebVisit()?.Browser;
            var occurrences = new List<VisitGroupMeasurement<BrowserData>>();

            if (browserData != null)
            {
                var serializedBrowserDataKey = JsonConvert.SerializeObject(browserData, new JsonSerializerSettings
                {
                    TypeNameHandling = TypeNameHandling.All
                });

                occurrences.Add(new VisitGroupMeasurement<BrowserData>(new VisitGroup(serializedBrowserDataKey), new[] { browserData }));
                return occurrences;
            }

            return occurrences;
        }
    }
```

The `BrowserData` JSON might be too large to be used as a key in the metric, and it has a lot of unwanted information. You must use a key resolver to use the correct information as the key.

To create a key resolver:

- Create a class that extends the `IVisitGroupKeyResolver<String>` interface:

    ```csharp
    class BrowserKeyResolver: IVisitGroupKeyResolver<string>
        {
            public IEnumerable<string> GetKeys(IVisitGroup visitGroup)
            {
                if (visitGroup == null)
                {
                    throw new ArgumentNullException(nameof(visitGroup));
                }

                var browserDataKey = JsonConvert.DeserializeObject<BrowserData>(visitGroup.Id, new JsonSerializerSettings
                {
                    TypeNameHandling = TypeNameHandling.All
                });

                return new List<string>
                {
                    browserDataKey.BrowserMinorName}
                };
            }
        }
    ```
    > [!note]
    > If you use `Guid` or `string` for the `VisitGroup` ID, `BrowserKeyResolver` becomes optional.

### Create a fact calculator

You must create a fact calculator. This is the class that calculates the values of individual metrics defined in your model for this dimension, for example number of page views, bounce rate, and so on.

To create a fact calculator:

1. Create a class the extends the `IFactCalculator<out TValue, TGroup>` interface, where `TValue` is the type of the first class you created and `TGroup` is the group type in the `IGroupResolver` class you created.
2. Implement the `CalculateFactsForGroup` method. This method calculates the individual metrics for the specific groups.

The following example measures the total engagement value, the number of visits, and the bounces that happened during the visit:

```csharp
class BrowserFactCalculator : IFactCalculator<BrowserMetrics, BrowserData>
    {
        public BrowserMetrics CalculateFactsForGroup(VisitGroupMeasurement<BrowserData> groupMeasurement, IInteractionAggregationContext context)
        {
            if (groupMeasurement == null)
            {
                throw new ArgumentNullException(nameof(groupMeasurement));
            }
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            var pageViewEvents = context.Interaction.Events.OfType<PageViewEvent>().ToList();

            return new BrowserMetrics
            {
                Bounces = pageViewEvents.Count == 1 ? 1 : 0,
                EngagementValue = context.Interaction.EngagementValue,
                Visits = 1
            };
        }
    }
```

### Create a parent key resolver

This step is optional. If you have a series of dimensions that are hierarchical in nature, for example outcome groups that contain many outcomes, you can implement a parent key resolver to represent the relationship in the hierarchy.

To create a parent key resolver:

- Create a class that extends either the `IVisitGroupKeyResolver<GUID>` or the `IVisitGroupKeyResolver<String>` interface.

For example, you want to be able to drill down from an browser group to a specific version of the browser. To do this, you must resolve the browser group and its associated group keys by implementing the `GetKeys` method. This method returns the GUID of the browser group parent item:

```csharp
class BrowserParentKeyResolver : IVisitGroupKeyResolver<string>
    {
        public IEnumerable<string> GetKeys(IVisitGroup visitGroup)
        {
            if (visitGroup == null)
            {
                throw new ArgumentNullException(nameof(visitGroup));
            }

            var browserDataKey = JsonConvert.DeserializeObject<BrowserData>(visitGroup.Id, new JsonSerializerSettings
            {
                TypeNameHandling = TypeNameHandling.All
            });

            return new List<string>
            {
                browserDataKey.BrowserMajorName
            };
        }
    }
```

### Create an interaction filter

This step is optional. You can implement interaction filters for your dimension. You use an interaction filter to ignore data related to an interaction, based on events, goals, campaigns, channels, contacts, or downloads. For example, if you do not want a certain page on your website to appear as part of your reports, you create a filter that specifies that the page should be ignored.

To create an interaction filter:

- Create a class that extends the `IInteractionFilter` interface and implements the `KeepInteraction `method.  

In the following example you restrict processing so that only data for interactions that have events of a specific type based on their definition GUID are calculated:

```csharp
class EventFilter : IInteractionFilter
{
  public bool KeepInteraction(Interaction interaction)
  {
     if (interaction.Events.Any(data => ReservedIds.Contains(data.DefinitionId)))
     {
         return true;
     }

     return false;
  }

   internal static readonly Guid[] ReservedIds =
{
// list of guids
};
}
```

</details>

<details>
<summary><h2>Update the reporting database</h2></summary>

You must update the reporting database to store data for the new dimension. You create a table to store results, you create a type to represent the data, and you create a stored procedure to manipulate the data stored in the table.

### Create a table

Create a table in SQL with a name following this convention: `Fact_`*YourDimensionName*`Metrics` where *YourDimensionName* is the dimension name you used when you created your class.

When you create the table several columns are mandatory and required by Sitecore:

- SegmentRecordId
- SegmentId
- Date
- SiteNameId
- DimensionKeyId
- FilterId

You must create a column to represent each metric property defined on your model class and the names must match. This is an example of a script to do this:

```text
CREATE TABLE [dbo].[Fact_BrowserMetrics](
     -- Mandatory Columns
            [SegmentRecordId] [bigint] NOT NULL,
     [SegmentId] [uniqueidentifier] NOT NULL,
     [Date] [smalldatetime] NOT NULL,
     [SiteNameId] [int] NOT NULL,
     [DimensionKeyId] [bigint] NOT NULL,
     [FilterId] [uniqueidentifier] NULL,
            -- Dimension specific metric columns
     [Visits] [int] NOT NULL,
     [EngagementValue] [int] NOT NULL,
     [Bounces] [int] NOT NULL,
CONSTRAINT [PK_Fact_BrowserMetrics_1] PRIMARY KEY CLUSTERED
     (
     [SegmentRecordId] ASC
     )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
     ) ON [PRIMARY]
```

### Create a type and a stored procedure

You use a type and a stored procedure to manipulate the data in your table. The type must match the structure of the table exactly. This is an example:

```text
CREATE TYPE [dbo].[BrowserMetrics_Type] AS TABLE(
     -- Mandatory Columns
     [SegmentRecordId] [bigint] NOT NULL,
     [SegmentId] [uniqueidentifier] NOT NULL,
     [Date] [smalldatetime] NOT NULL,
     [SiteNameId] [int] NOT NULL,
     [DimensionKeyId] [bigint] NOT NULL,
     [FilterId] [uniqueidentifier] NULL,

     -- Dimension specific metric columns
     [Visits] [int] NOT NULL,
     [EngagementValue] [int] NOT NULL,
     [Bounces] [int] NOT NULL,
     PRIMARY KEY CLUSTERED
     (
     [SegmentRecordId] ASC
     )WITH (IGNORE_DUP_KEY = OFF)
     )
```

The stored procedure must create a record in the table if this does not exist, or add the incoming values to an existing record, and capture any errors. This is an example:

```text
CREATE PROCEDURE [dbo].[Add_BrowserMetrics_Tvp]
       @table [dbo].[BrowserMetrics_Type] READONLY
     AS
     BEGIN
       SET NOCOUNT ON;

       BEGIN TRY

         MERGE [Fact_BrowserMetrics] AS Target USING @table AS Source
         ON
           Target.[SegmentRecordId] = Source.[SegmentRecordId]
         WHEN MATCHED THEN
           UPDATE SET
             Target.[Visits] = (Target.[Visits] + Source.[Visits]),
             Target.[EngagementValue] = (Target.[EngagementValue] + Source.[EngagementValue]),
             Target.[Bounces] = (Target.[Bounces] + Source.[Bounces])
         WHEN NOT MATCHED THEN
           INSERT (
     [SegmentRecordId],
     [SegmentId],
     [Date],
     [SiteNameId],
     [DimensionKeyId],
     [FilterId],
     [Visits],
     [EngagementValue],
     [Bounces]
           )
           VALUES (
     Source.[SegmentRecordId],
     Source.[SegmentId],
     Source.[Date],
     Source.[SiteNameId],
     Source.[DimensionKeyId],
     Source.[FilterId],
     Source.[Visits],
     Source.[EngagementValue],
     Source.[Bounces]
     );

       END TRY
       BEGIN CATCH

         DECLARE @error_number INTEGER = ERROR_NUMBER();
         DECLARE @error_severity INTEGER = ERROR_SEVERITY();
         DECLARE @error_state INTEGER = ERROR_STATE();
         DECLARE @error_message NVARCHAR(4000) = ERROR_MESSAGE();
         DECLARE @error_procedure SYSNAME = ERROR_PROCEDURE();
         DECLARE @error_line INTEGER = ERROR_LINE();

         RAISERROR( N'T-SQL ERROR %d, SEVERITY %d, STATE %d, PROCEDURE %s, LINE %d, MESSAGE: %s', @error_severity, 1, @error_number, @error_severity, @error_state, @error_procedure, @error_line, @error_message ) WITH NOWAIT;
       END CATCH;
     END
```

</details>

<details>
<summary><h2>Create items for dimensions and metrics</h2></summary>

The next steps are to create Sitecore definition items that represent your dimension and metrics. You use these items when you create new reports.

### Create the metrics items

To create a metrics item:

1. In the **Content Editor**, navigate to the *FlexibleMetrics* folder in the *Core* database (`sitecore``/Client/Applications/ExperienceAnalytics/Common/System/ChartFields/FlexibleMetrics`).
2. For each metric associated with your new dimension, in the **Home** tab, in the **Insert** group, select, insert, and name a *MetricChartField*. A field can already exist because metrics can be shared across multiple dimensions. Following the example in the previous sections, you need to create a definition item for the `EngagementValue` custom metric.
3. Select the new item and specify values in these fields:
    - In the **Appearance** section, in the **HeaderText** field, enter a descriptive name for the new metric.
    - In the **DataBinding** section, in the **DataField** field enter the name (in camelCase) of the corresponding column in the table you have created.
4. Save.

### Create and configure the dimension item

To create and configure the dimension item:

1. In the **Content Editor**, navigate to the *Dimensions* folder in the **Marketing Control Panel** in the Master database (*Marketing Control Panel/Experience Analytics/Dimensions*) and select the subfolder to store your flexible dimension item in. You can choose *Pages*, *Visits*, or create your own subfolder.
2. On the **Home** tab, in the **Insert** group, select, insert, and name a *FlexibleDimension* item.
3. Select the new item and specify values in these fields:
    - In the **Data** section, in the **Metric Type** field, enter the name of the class you created (for example `BrowserMetrics`).
    - In the **Name** field, enter a descriptive name.
    - In the **Metrics** field, select all metrics you want to add to the dimension.
    - Save.

### Create a segment item

Experience Analytics uses segments to filter data into smaller sets when aggregating. You must specify at least one segment before any data is aggregated. You normally specify an all interactions/visits segment for this purpose. [Create a custom report filter and segment](https://doc.sitecore.com/xp/en/users/104/sitecore-experience-platform/create-a-custom-report-filter-and-segment.html) has more information.

To create a segment:

1. In the **Content Editor**, navigate to */sitecore/System/Marketing Control Panel/Experience Analytics/Dimensions*, and select the dimension you created.
2. In the **Home** tab, in the **Insert** group, select, insert, and name a *Segment* item.
3. In the **Review** tab, in the **Workflow** group, click **Deploy**. By default, this starts data collection for the segment 30 minutes after deployment.

> [!note]
> Do not change a segment after you have deployed it. If you do, it can cause data inconsistencies.

</details>

<details>
<summary><h2>Configure the new dimension</h2></summary>

You must specify the new dimension in the Sitecore configuration to invoke it during aggregation.

To configure the new dimension:

1. Create a patch file similar to this:

    ```xml
    <configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
        <sitecore>
            <experienceAnalytics>
                <aggregation>
                    <flexibleDimensions>
                        <!--Add references to your new dimension here -->
    <dimension id="Guid of your new dimension item" type="Sitecore.ExperienceAnalytics.Aggregation.FlexibleMetrics.Framework.FlexibleDimension`2[[Metrics Class fully qualified name, metrics class assembly name], [Fully qualified name of Group, Assembly name of Group]], Sitecore.ExperienceAnalytics.Aggregation">

      <param type="GroupResolver fully qualified name, GroupResolver assembly name" />

      <param type="FactCalculator fully qualified name, FactCalculator assembly name"/>
    </dimension>
                    </flexibleDimensions>
                </aggregation>
            </experienceAnalytics>
        </sitecore>
    </configuration>
    ```

2. Optionally, specify parent key resolvers and filters by adding additional `param` elements to your dimension:

    ```xml
    <dimension …>

    ...

    <param type="Sitecore.ExperienceAnalytics.Aggregation.FlexibleMetrics.Framework.KeyComposition.HierarchicalKeyComposer`1[[System.Guid]], Sitecore.ExperienceAnalytics.Aggregation">
      <ParentVisitGroupKeyResolvers hint="list">
        <ParentVisitGroupKeyResolver type="ParentKeyResolver fully qualified name, assembly name" />
      </ParentVisitGroupKeyResolvers>
    </param>

    <InteractionFilters hint ="list">
      <InteractionFilter type="Filter fully qualified name, assembly name" />
    </InteractionFilters>
    </dimension>
    ```

</details>