Workspace Assembly Dynamic Dimensions

Dynamic Dimension Services execute assembly files for dynamic dimensions. The assembly file uses public classes to construct the DynamicDimensionInfo object, which creates the Dynamic Dimension. The DynamicDimensionInfo object uses the following syntax:

Syntax: DynamicDimensionInfo(ContentTimeStamp, NumSecondsBeforeReadContentTimeStamp, DynamicMemberCollection, DynamicRelationshipCollection)

For additional details on required objects and syntax, refer to Dynamic Cube Services API Reference.

When designing a dynamic dimension, consider the following:

  • Frequency and trigger for the dimension to update during workspace or assembly file changes, or time-based updates (daily, hourly, or by the minute). For details, see Frequency and Trigger.

  • Member definitions, including properties, relationships, and aggregation factors. For details, see Member Definitions.

  • Metadata Enrichment of dynamic members for properties, constraints, and member formulas. For details, see Metadata Enrichment.

Frequency and Trigger

Each application server processes and caches metadata using a simple algorithm. When a dimension object is created, it gets a timestamp and a wait period before checking for updates. During this period, the cached object is returned from memory for faster access. After the timeout, the system calls ReadDynamicDimensionContentTimestamp to decide whether to recreate the object or continue using the cached version.

Example: The timeout ("X seconds" in the diagram above) is defined with the NumSecondsBeforeReadContentTimestamp argument of the dimension constructor:

Syntax: DynamicDimensionInfo( __, NumSecondsBeforeReadContentTimeStamp,__ ,__ )

Selection of such value should be based on the expected frequency of changes in the dimension source. If the cadence is unknown, or the members are manually created in the assembly file, the update can be triggered through changes to the assembly file by using the time variable SharedConstants.DateTimeMinValue in ReadDynamicDimensionContentTimestamp.

The logic in ReadDynamicDimensionContentTimestamp will naturally determine how often the dimension is recreated. If you use a higher refresh frequency, such as by minute, application performance may be impacted, especially with high volumes.

Refer to the following table for time-based refresh trigger rates:

Refresh Type Example ReadDynamicDimensionContentTimestamp implementation
Daily Refresh

Dim now = Datetime.now

Return new DateTime( now.Year, now.Month, now.Day, 0, 0, 0, 0, DateTimeKind.utc)

Hourly Refresh

Dim now = Datetime.now

Return new DateTime( now.Year, now.Month, now.Day, now.Hour, 0, 0, 0, DateTimeKind.utc)

Minute Refresh

Dim now = Datetime.now

Return new DateTime( now.Year, now.Month, now.Day, now.Hour, now.Minute, 0, 0, DateTimeKind.utc)

Member Definitions

After a dimension is created using the Source Code Type of Dynamic Dimension Service, all metadata management is performed through workspace assembly files. Members can be created and defined in the assembly file. The assembly file can also source members and define properties from external systems, such as a database table. The members may be defined as either base or parent.

A new dynamic dimension service assembly file is created and populated with sample code that exposes the public classes and functions.

The assembly file creates the member and its relationship to parents in the dimension hierarchy. These objects are then passed to the DynamicDimensionInfo constructor. This object includes a data-time property that supports controlling and triggering the dimension refresh.

Syntax: DynamicDimensionInfo( ContentTimestamp, numSecodsBeforeCheckingTimestamp , DynamicMemberCollection, DynamicRelationshipCollection)

The following objects will have to be created to support the constructor:

  • DynamicMemberCollection: The list of members in the dimension. Note: when specifying member names, if they contain unsupported special characters, they will be automatically stripped.

  • DynamicRelationshipCollection: The list of parent-child relationship between members, defining the hierarchy structure. Note that it should also contain the relationship between any top member and Root, or between top members and members of an inherited (extended) dimension.

For additional details, refer to Dynamic Cube Services API Reference.

Note that the system provides a shortcut method to build the entire dimension (and attach it to the Root member) with a single SQL query. See the API section for further details.

If the resulting dimension must be nested under an existing member instead of Root, the relationship can be modified like this:

Copy
Dim dynamicDim = api.ReadDynamicDimensionInfo(...)
dynamicDim.Relationships.GetAllRelationships().Find(Function(r) r.ParentId.Equals(DimConstants.Root)).ParentId = newParentId 
Copy
{
    var dynamicDim = api.ReadDynamicDimensionInfo(...);
    dynamicDim.Relationships.GetAllRelationships().Find(r => r.ParentId.Equals(DimConstants.Root)).ParentId = newParentId;
}

Enabling the Service

The Dynamic Dimension service must be returned by a Service Factory.

Example:

Copy
Select Case wsasType
Case Is = WsAssemblyServiceType.DynamicDimension
     ' we can switch on the dimension name
     If itemName.XFEqualsIgnoreCase("MyUD1") Then
          Return New MyNewDynamicDimension()
Copy
{
    switch (wsasType)
    {
        case object _ when wsasType == WsAssemblyServiceType.DynamicDimension:
            {
                // we can switch on the dimension name
                if (itemName.XFEqualsIgnoreCase("MyUD1"))
                    return new MyNewDynamicDimension();
                break;
            }
    }
}

See Workspaces > Configure the Service Factory in the Design and Reference Guide for more information.

Extensibility

Dynamic dimension assembly files can reference a stored dimension member to support extensibility. Standard dimensions should never be extended from dynamic dimensions. Extending a dynamic dimension with another dynamic dimension is possible, but not recommended.

Metadata Enrichment

When objects are initiated as dynamic members or a relationship collection, the objects can be enriched to configure all stored and vary-by properties. Members created within the assembly file, or originating in source systems, can be configured with all required and optional properties, including vary-by-ScenarioType and Time properties. Example:

Copy
Dim dynmem As DynamicMember = myDynamicDimensionInfo.Members.GetMember("My Member Name")
dynmem.VaryingMemberProperties.GetUDProperties().Text1.SetStoredValue(ScenarioType.Unknown.id, DimConstants.Unknown, "Something")
Copy
{
    DynamicMember dynmem = myDynamicDimensionInfo.Members.GetMember("My Member Name");
    dynmem.VaryingMemberProperties.GetUDProperties().Text1.SetStoredValue(ScenarioType.Unknown.id, DimConstants.Unknown, "Something");
}

Example

The following example implements a simple dynamic dimension from an SQL table.

Copy
Namespace Workspace.__WsNamespacePrefix.__WsAssemblyName
    Public Class MyDynamicDimensionService
        Implements IWsasDynamicDimensionV800
        Function ReadDynamicDimensionContentTimestamp(ByVal si As SessionInfo, ByVal api As IWsasDynamicDimensionApiV800, ByVal args As DynamicDimensionArgs) As DateTime  Implements IWsasDynamicDimensionV800.ReadDynamicDimensionContentTimestamp
            Try
                ' Return SharedConstants.DateTimeMinValue if you are unable to determine 
                ' when the underlying dimension members or relationships have been changed.
                ' In that case, the cache for this dimension will only be refreshed when 
                ' a user edits this dimension's properties, this workspace's properties, 
                ' or this workspace assembly's code.
                Return SharedConstants.DateTimeMinValue

            Catch ex As Exception
                Throw New XFException(si, ex)
            End Try
        End Function
        Public Function ReadDynamicDimensionInfo(ByVal si As SessionInfo, ByVal api As IWsasDynamicDimensionApiV800, ByVal args As DynamicDimensionArgs) As DynamicDimensionInfo Implements IWsasDynamicDimensionV800.ReadDynamicDimensionInfo
            Try
                ' retrieve parameters from Dimension configuration
                Dim nameValuePairs = New NameValueFormatBuilder(args.Dim.DimMemberSourceNVPairs)
                Dim secsToWait As Integer = nameValuePairs.NameValuePairs.XFGetValue("SecsToWait", 5)

                ' Define timestamp
                Dim contentTimestamp As DateTime = DateTime.Now

                ' build the dimension from a query on the Application database
                Dim dynamicDim = api.ReadDynamicDimensionInfo(si, args, contentTimestamp, _
                        SharedConstants.Unknown, api.DbConnApp, _
                        "SELECT Child, Description, Parent FROM MyCustomDimensionTable", _
                        Nothing, Nothing, "Child", "Description", _
                        String.Empty, String.Empty, "Parent", String.Empty, String.Empty, String.Empty, False)

                ' Tweak the aggregation weight for one member
                Dim someMemberID As Integer = dynamicDim.Members.GetMember("MyMember").Member.MemberId
                dynamicDim.Relationships.GetAllRelationships().Find(Function(r) r.ChildId.Equals(someMemberID)).UDAggWeight = 0.0

                ' Set a text property on a member
                Dim dynmem As Integer = dynamicDim.Members.GetMember("AnotherMember")
                dynmem.VaryingMemberProperties.GetUDProperties().Text1.SetStoredValue( _
                    ScenarioType.Unknown.id, DimConstants.Unknown, "Something")

                ' return the hierarchy
                Return dynamicDim
            Catch ex As Exception
                Throw New XFException(si, ex)
            End Try
        End Function
    End Class
End Namespace
Copy
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Text;
using System.Threading.Tasks;
using Microsoft.VisualBasic;

namespace TestProject.Workspace.__WsNamespacePrefix.__WsAssemblyName
{
    public class MyDynamicDimensionService : IWsasDynamicDimensionV800
    {
        public DateTime ReadDynamicDimensionContentTimestamp(SessionInfo si, IWsasDynamicDimensionApiV800 api, DynamicDimensionArgs args)
        {
            try
            {
                // Return SharedConstants.DateTimeMinValue if you are unable to determine 
                // when the underlying dimension members or relationships have been changed.
                // In that case, the cache for this dimension will only be refreshed when 
                // a user edits this dimension's properties, this workspace's properties, 
                // or this workspace assembly's code.
                return SharedConstants.DateTimeMinValue;
            }
            catch (Exception ex)
            {
                throw new XFException(si, ex);
            }
        }
        public DynamicDimensionInfo ReadDynamicDimensionInfo(SessionInfo si, IWsasDynamicDimensionApiV800 api, DynamicDimensionArgs args)
        {
            try
            {
                // retrieve parameters from Dimension configuration
                var nameValuePairs = new NameValueFormatBuilder(args.Dim.DimMemberSourceNVPairs);
                int secsToWait = nameValuePairs.NameValuePairs.XFGetValue("SecsToWait", 5);

                // Define timestamp
                DateTime contentTimestamp = DateTime.Now;

                // build the dimension from a query on the Application database
                var dynamicDim = api.ReadDynamicDimensionInfo(si, args, contentTimestamp, SharedConstants.Unknown, api.DbConnApp, "SELECT Child, Description, Parent FROM MyCustomDimensionTable", null/* TODO Change to default(_) if this is not a reference type */, null/* TODO Change to default(_) if this is not a reference type */, "Child", "Description", string.Empty, string.Empty, "Parent", string.Empty, string.Empty, string.Empty, false);

                // Tweak the aggregation weight for one member
                int someMemberID = dynamicDim.Members.GetMember("MyMember").Member.MemberId;
                dynamicDim.Relationships.GetAllRelationships().Find(r => r.ChildId.Equals(someMemberID)).UDAggWeight = 0.0;

                // Set a text property on a member
                int dynmem = dynamicDim.Members.GetMember("AnotherMember");
                dynmem.VaryingMemberProperties.GetUDProperties().Text1.SetStoredValue(ScenarioType.Unknown.id, DimConstants.Unknown, "Something");

                // return the hierarchy
                return dynamicDim;
            }
            catch (Exception ex)
            {
                throw new XFException(si, ex);
            }
        }
    }
}