Read Dynamic Data Unit

Workspace Assembly files are designed to emulate the content of data record tables. The core definition must construct a list of data records that contain the required content of a data unit. This allows the Finance engine to properly interpret the data. The resulting data, which can be numeric or text-based, is in memory cached dynamic data.

A data unit consists of these objects:

  • Entity

  • Parent

  • Scenario

  • Consolidation

  • Time (Year)

A dynamic data service enables a dynamic cube to read and write data from an internal or external source.

The file contains three functions to be implemented:

  • ReadDynamicDataUnitContentTimeStamp: Return a timestamp representing when the data might have changed. This will be used to determine whether cached data should be refreshed. Refer to Workspace Assembly Dynamic Dimensions.htm.

  • ReadDynamicDataUnitData: Return a DynamicDataUnitData object representing the requested DataUnit. The object can be populated with data coming from anywhere, including external databases and existing cubes. .

  • SaveDynamicDataCells: This function will receive cells modified via forms, giving developers the ability to send data to external systems or custom tables. .

When creating a DynamicDataUnitData object to implement ReadDynamicDataUnitData, specify a timestamp indicating when the returned data was last changed ( [ contentTimestamp ] ) and how long to wait before the timestamp should be checked again ( numSecondsBeforeReadingContentTimeStamp).

Refresh triggering options occur upon a change to the workspace assembly file. This occurs daily, hourly, and by the minute. Refer to the following example.

Data can be provided to the DynamicDataUnitData object as a DataRecord object or DataBufferCellPk:

  • DataRecord: One or more objects, representing all values in the year for a specific intersection. For example, having 12 values in a monthly application. See DataRecord.

  • DataBufferCellPk: Representing a specific intersection for a specific period only. For example, a single value in a single period, be it YTD or Periodic. See DataBufferCellPk.

DataRecord

DataRecord objects are typically created by specifying a DataRecordPk object (which contains the record coordinates, i.e. the members that define the intersection), and the number of cells it should have (one for each period in the year, e.g. 12 in a monthly application).

Copy
' define the Pk
Dim drPk As New DataRecordPk( api.Pov.Cube.CubeId, _
    api.Pov.Entity.MemberId, _
    DimConstants.Unknown, _     ' this is parentId, optional at base level
    api.Pov.Cons.MemberId, api.Pov.Scenario.MemberId, _
    2025, _         ' use straight years here, not "timeId" ones
    accountId, flowId, originId, icId, _
    u1id, u2id, u3id, u4id, u5id, u6id, u7id, u8id)
' create the record
Dim newRecord as new DataRecord(drPk, 12)
Copy
// define the Pk
var drPk = new DataRecordPk(api.Pov.Cube.CubeId,
    api.Pov.Entity.MemberId,
    DimConstants.Unknown,  // this is parentId, optional at base level
    api.Pov.Cons.MemberId, api.Pov.Scenario.MemberId,
    2025,  // use straight years here, not "timeId" ones
    accountId, flowId, originId, icId,
    u1id, u2id, u3id, u4id, u5id, u6id, u7id, u8id);
// create the record
var newRecord = new DataRecord(drPk, 12);

NOTE: When using dynamic dimensions, IDs are automatically generated, and member names could overlap with members or regular stored dimensions. The recommended best practice is to retrieve the dynamic member ID using the external ID or name.

Copy
Dim accId = api.Members.GetMemberUsingExternalMemberId( _
                 api.Pov.AccountDim.DimPk, myExternalId).MemberId
Copy
{
    var accId = api.Members.GetMemberUsingExternalMemberId(api.Pov.AccountDim.DimPk, myExternalId).MemberId;
}

Values can then be set in the record by manipulating the DataCells list of DataCacheCell objects. Note that YTD values are recommended for best performance.

Copy
' index determines the period - 0 is January, 1 is February, etc
newRecord.DataCells(0).SetData(myDecimalAmount, DataCellExistenceType.IsRealData, DataCellStorageType.NotStored)
newRecord.DataCells(1) = new DataCacheCell(myDecimalAmount, new DataCellStatus(DataCellExistenceType.IsRealData, DataCellStorageType.NotStored)
Copy
{
    // index determines the period - 0 is January, 1 is February, etc
    newRecord.DataCells(0).SetData(myDecimalAmount, DataCellExistenceType.IsRealData, DataCellStorageType.NotStored);
    newRecord.DataCells(1) = new DataCacheCell(myDecimalAmount, new DataCellStatus(DataCellExistenceType.IsRealData, DataCellStorageType.NotStored));
}

DataRecord objects can then be added to the DynamicDataUnitData instance using the following methods:

  • setDataRecord: To assign an individual instance

  • setDataRecords: To assign a List or other collection of instances in bulk.

DataBufferCellPk

DataBufferCellPk objects represent individual values, and can be added directly to the DynamicDataUnitData object with the following methods:

  • SetDataCell: Sets numeric values to a defined time at the V#YTD or V#Periodic members.

  • SetDataCellAnnotation: Sets text values to a defined time at the V#Annotation member.

  • SetDataCellUsingCellIndex: Sets numeric values to a zero-based time index supporting full data unit emulation at the V#YTD or V#Periodic members.

  • SetDataCellAnnotationUsingCellIndex: Sets text values to a zero-based time index supporting full data unit emulation at the V#Annotation member.

Copy
dynamicDataUnitData.SetDataCellUsingCellIndex(si, dataCellMonthIndex, dataBufferCellPk, amount, DataCellExistenceType.IsRealData, DataCellStorageType.Input);
Return dynamicDataUnitData
Copy
{
    dynamicDataUnitData.SetDataCellUsingCellIndex(si, dataCellMonthIndex, dataBufferCellPk, amount, DataCellExistenceType.IsRealData, DataCellStorageType.Input);/* TODO ERROR: Skipped SkippedTokensTrivia */
    return dynamicDataUnitData;
}

The workspace assembly file should define all the base-level dimension intersections that are valid in the dynamic cube. Data will populate all the undefined members available to the data buffer. Data buffer members can be fixed to a base level member if they are not definable in the source system, such as the origin dimension targeting Import, AdjInput or Forms. Without reference, the consolidation dimension will be populated at local and translated. Local and specified currencies can be targeted.

NOTE: The generated dataunit will not be further filtered by the system on the main dimensions (Year, Scenario, Entity, Parent, Consolidation, and Cube). The system assumes that all records and cells appearing in the DynamicDataUnitData object belong to the dataunit that was requested, regardless of any DataRecordPK or DataBufferCellPK attached to the object. For example, if the requested dataunit was for entity Chicago and we added records pointing to entity New York, those records will still appear as pointing to Chicago !

Caching

DataUnits are cached in memory. For best performance, developers should ensure that data is served from such cache as much as possible; every execution of ReadDynamicDataUnitData will be significantly slower than just reusing cached data.

The algorithm used by OneStream is similar to the one seen for Dynamic Dimensions:

Example: In the diagram above, "X seconds" is the value passed to the DynamicDataUnitData constructor as argument NumSecondsBeforeCheckingTimeStamp; likewise, the Timestamp is passed as the other argument to the same constructor.

It is not possible to externally invalidate the cache. Because this logic is executed on each application server individually, if source data changes and caching timeouts are very long, a situation might arise where different users connected to different servers might observe different data until the timeout expires. If this is not acceptable, developers can set the timeout to a short value and add logic in ReadDynamicDataUnitContentTimestamp to look up a timestamp from a shared source (for example, a custom table); for best performance, this logic should be kept to a minimum, since this method could be called very often and by multiple processes.