---
title: Map stores
slug: cms-13/docs/map-stores
docTags: 
createdAt: 2026-07-30T20:22:04.871Z
---

Dynamic Data Store (DDS) maps .NET data types to database columns so that object properties persist and restore automatically. This article covers compile-time and runtime type mapping, store remapping, and routing types to specific stores.

## Compile-time type mapping

Define how DDS translates .NET class properties into store columns at compile time.

When instances of a compile-time data type (.NET classes excluding `EPiServer.Data.Dynamic.PropertyBag` and classes implementing `System.IEnumerable`) are saved in Dynamic Data Store, their "inline" properties map to columns in the "big table." This combination of mapped columns is known logically as a store.

### Default mapping

Understand which properties DDS maps automatically without additional configuration.

The default algorithm for mapping .NET classes (excluding `EPiServer.Data.Dynamic.PropertyBag` and classes implementing `System.IEnumerable`) to a store follows these rules:

- Property must have a getter and setter
- Property must be marked public (although the setter can mark non-public if desired)
- Other properties are ignored and not saved in the Dynamic Data Store

### Custom mapping

Override the default mapping to include non-public properties or exclude public ones.

Custom mapping controls which properties DDS persists. Add the `System.Runtime.Serialization.DataContractAttribute` to the class definition. Only properties marked with the `System.Runtime.Serialization.DataMemberAttribute` are then mapped and saved in Dynamic Data Store, regardless of accessibility. They must still have a getter and setter.

Some existing classes already use `DataContractAttribute` and `DataMemberAttribute` for other purposes. If the existing attribute configuration does not match the desired DDS behavior, add the `EPiServerDataContractAttribute` to the class definition and `EPiServerDataMemberAttribute` to the properties to save. Dynamic Data Store uses these attributes in preference to the Microsoft ones to resolve the conflict.

### Type handlers

Register type handlers for .NET classes whose properties DDS cannot extract and restore automatically.

Some .NET Framework classes lack writable properties that DDS requires to extract, save, and restore values. To use such a type as a property on a class saved to Dynamic Data Store, register a type handler for it.

For example, the `System.Uri` class has only read-only properties. Saving an instance to Dynamic Data Store without a type handler stores no data.

## Map runtime data type (PropertyBag)

Map dynamic property sets at runtime using `PropertyBag` objects instead of compiled .NET classes.

Properties saved using `PropertyBag` objects are mapped as if they were public member properties on a standard .NET class. `PropertyBag` objects support two mapping approaches:

- **Implicit** – The first time a `PropertyBag` is saved to a store, DDS infers the store mappings from the properties in the `PropertyBag`.
- **Explicit** – A mapping dictionary is passed to the `DynamicDataStoreFactory.CreateStore` method to define the store mapping. This approach maps all properties that the store will accept, not only those present on the first save.

## Store remapping

Update store mappings when .NET class properties or `PropertyBag` structures change.

Data structures evolve over time. Changing properties on .NET classes or `PropertyBag` objects requires remapping the store. Dynamic Data Store accepts structural changes to types saved in a store.

Remap .NET classes to stores using the following approaches:

- Using attributes on the class.
- Using attributes with the `StoreDefinition` class. Remapping stores that are not represented by a .NET class can only be done with the `StoreDefinition` class.

### Remap using class attributes

Automate store remapping at application startup by decorating a .NET class with `EPiServerDataStoreAttribute`.

Set the `AutomaticallyRemapStore` property to `true` on the attribute. When Optimizely starts, it scans for classes with this attribute and remaps the .NET class to the store if needed. Any renamed properties must be marked with the `EPiServerDataPropertyRenameAttribute` attribute. Otherwise, the remap treats them as if one property was removed (with the old name) and one was added (with the new name).

:::hint{type="danger"}
**Warning**
In multi-instance environments (such as Azure App Service or load-balanced servers), `AutomaticallyRemapStore = true` can cause transient `Invalid column name` errors when you add, remove, or rename properties. See [Multi-instance deployment considerations](docId:-ztPurfZEQEl1iLb4ZszZ).
:::

### Remap using StoreDefinition class

Remap stores programmatically using the `StoreDefinition` class, especially for stores not represented by a .NET class.

Obtain the store definition through the `StoreDefinition` property of a `DynamicDataStore` instance or through the `StoreDefinition.Get` method.

Call `RenameProperty` before `Remap` to update the store mappings. Otherwise, renamed properties in the data type are treated as if one property was removed and another was added.

After remapping, call `CommitChanges` on `StoreDefinition` to update the store metadata in the database. If a `DynamicDataStore` instance reference exists, call its `Refresh` method to align the in-memory store definition with the committed version.

### Remap rules

DDS follows these rules when remapping stores:

1. Properties removed from the type definition are removed from the store.

:::hint{type="info"}
**Note**
The data for the removed property remains intact in the big table. Only the “view” of the data changes.
:::

2. Properties added to the type definition are added to the store.
3. Properties with the same name but different data types are checked for compatibility. When old and new property data types are 'inline,' the database used must be able to convert from the old database data type to the new one. If a collection or reference type is changed, then the new property type must be assignable (using `System.Type.IsAssignableFrom`) or the old type must be convertible to the new type. The old type supports `System.IConvertible` and the `System.Convert.ChangeType` succeeds in converting an instance of the old type to an instance of the new type.

For examples of store remapping and property renaming, see the Dynamic Data Store SDK samples.

## Multi-instance deployment considerations

Avoid transient SQL errors caused by uncoordinated schema changes across multiple instances during deployment.

When `AutomaticallyRemapStore = true`, Optimizely applies schema changes to a Dynamic Data Store (DDS) type during application startup. Schema changes include adding, removing, or renaming properties. In a single-instance environment, this is safe because no requests are served until startup completes. In a multi-instance environment, several instances may start at the same time during a deployment. Optimizely does not coordinate the schema change between them.

This can lead to a race condition:

- One instance starts applying the schema change (such as adding a column) while another queries the same store before the change finishes.
- The second instance reads the database in an intermediate state and throws a SQL exception, such as `Invalid column name 'RenditionUrl'`.
- The error typically appears 15–20 seconds after startup, as a SQL exception during a query rather than a schema update failure. It is usually transient and resolves after all instances complete the remap. However, requests served in the meantime can fail.

Environments where this applies include Azure App Service (especially with slot-swap deployments and multiple instances) and load-balanced server farms.

### Pre-create stores with an `IInitializableModule` for multi-instance deployment

Pre-create the affected stores in an `IInitializableModule` so the schema change completes before the instance serves requests. Because the module runs as part of the initialization pipeline, Optimizely creates the store and any pending remap before the instance handles requests.

```csharp
[InitializableModule]
[ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
public class DdsSchemaInitializer : IInitializableModule
{
    public void Initialize(InitializationEngine context)
    {
        // Pre-create stores before serving requests
        DynamicDataStoreFactory.Instance.CreateStore(typeof(YourDdsType));
    }
    public void Uninitialize(InitializationEngine context) { }
}
```

### Use Azure warm-up for multi-instance deployment

On Azure App Service, use the `WEBSITE_WARMUP_PATH` application setting with a health endpoint that calls `CreateStore()` for the affected types. Azure requests the warm-up path before adding the instance to the load balancer rotation. This ensures the schema change is complete before the instance receives traffic.

### Use rolling deployments for multi-instance deployment

Use rolling deployments so that only one instance restarts at a time. Each instance applies the schema change before the next instance starts.

## Map types to specific stores

Route all instances of a type to the same store, regardless of their position in an object graph.

Two mapping options control where type instances are saved:

1. **Global mapping** – Use the `EPiServer.Data.Dynamic.GlobalTypeToStoreMap.Instance` methods to add or remove a mapping. Alternatively, add `EPiServer.Data.Dynamic.EPiServerDataStoreAttribute` to the .NET class. Instances of the registered type are then saved in the store with the specified name.
2. **Local mapping** – Pass a delegate to the `DynamicDataStore` or `DynamicDataStore<T>` `Save` method. This delegate is called for "reference" properties and collection items that are references. The delegate returns the store name for the item, allowing instances of a type to be routed to different stores based on context.

:::hint{type="info"}
**Note**
The store top-level item (the object passed to the `DynamicDataStore` `Save` method) is always saved in the current store, regardless of type-to-store mappings. This overrides the mapping mechanism for the top-level object.
:::

Example:

1. A global type-to-store mapping is added so instances of the `Person` type are saved in a store called `People`.
2. A store is created or obtained with the name `MyPeople`.
3. An instance of `Person` is saved in the store obtained in step 2. This instance has properties that are also `Person` instances.

Result: The top-level `Person` is saved in the `MyPeople` store, but other `Person` instances in the object graph are saved in the `People` store because of the global mapping.

To follow the global type-to-store mappings, create or obtain the top-level store by calling `DynamicDataStoreFactory.CreateStore` or `GetStore` with just the type and not a store name.

## Map stores to custom big tables

Route a store to a custom big table for dedicated storage of specific data types.

A store can map to a custom big table, similar to how a type maps to a store. See the Big Table section in the [Dynamic Data Store](docId:_-YszkXr7OFiD8Xfj4tht) topic for details.
