Table of contents
- Assembly and Namespaces
- How Dynamic Data Store Works
- Identity Management
- Storing IEntity objects in Dynamic Data Store
- Supporting LINQ
- Compiling Time Type Mapping
- Mapping Runtime Data Type (PropertyBag)
- Storing Remapping
- Mapping Types to Specific Stores
- Mapping Stores to Custom Big Tables
- Indexing Properties
- Configuring Dynamic Data Store
- Recommendations for Usage
The Dynamic Data Store (DDS) is a component offering an API and infrastructure for the saving, loading and searching of both compile time data types (.NET object instances) and runtime data types (property bags). The component is shipped as part of the EPiServer Framework package.
Note that this document refers to a DDS sample project which can be downloaded from EPiServer World. The code samples are for EPiServer CMS 6 R2, but are applicable also for EPiServer 7.
The Dynamic Data Store is a new component offering an API and infrastructure for the saving, loading and searching of both compile time data types (.NET object instances) and runtime data types (property bags).
Alternative technologies include Microsoft’s Entity Framework 2.0 and NHibernate for .NET. However, the Dynamic Data Store has been specifically designed with EPiServer CMS and its flexible user-driven data in mind.
The EPiServer.Data assembly contains the following namespaces:
- EPiServer.Data namespace contains important classes used by in the Dynamic Data Store, most notably the Identity class.
- EPiServer.Data configuration contains the configuration classes for the Dynamic Data Store.
- EPiServer.Data.Dynamic namespace contains the DynamicDataStoreFactory and DynamicDataStore classes as well as their support classes and data structures.
- EPiServer.Data.Dynamic.Providers namespace contains the SqlServerDataStoreProvider class as well as their base classes and other database specific classes for LINQ support.
Use the DynamicDataStoreFactory class to create, obtain and delete stores. The class has a single instance which can be obtained from the static Instance property. Alternatively, stores can be automatically created for .NET classes by decorating them with the EPiServerDataStoreAttribute and setting the AutomaticallyCreateStore property to true.
See the UsingStores class in the DDS sample project for examples on creating, obtaining and deleting stores.
Saving and loading data
Data can be saved and loaded using compile time data types (.NET classes) and runtime data types via the EPiServer.Data.Dynamic.PropertyBag class. The Dynamic Data Store is divided into logical stores which are identified by name. Stores are not polymorphic which means only one property set may be saved in a store although it is possible to re-map stores and achieve a level of polymorphism though the use of interfaces and template types.
See the LoadSaveType and LoadSavePropertyBag classes in the the DDS sample project for examples of loading and saving data.
You can search data in the Dynamic Data Store in the following ways:
- Simple Find method. Find data structures by matching one or more name-value pairs with data in the store.
- LINQ. Find data structures using Microsoft’s Language Integrated Query technology.
See the UsingLinq and UsingFind classes in the DDS sample project for examples of searching for data.
The Dynamic Data Store is essentially an Object-Relational mapper. When used with compile time data types (.NET classes), all properties that have a public “getter” and a “setter” (setter does not need to be public) are mapped to a column in a database table. For runtime data types, each property added to a PropertyBag is also mapped in the same way.
The Dynamic Data Store uses the “big table” approach to storing data. That is by default, all data types are stored in one database table. This table contains many columns, several of each data type that the Dynamic Data Store supports.
When a data structure is saved, the .NET CLR type of each property is mapped against an internal list of supported types. The following types of mapping supported:
Inline mapping is where a property of a class or PropertyBag can be mapped directly against one of the supported “big table” database columns. The following types can be mapped inline:
- System.Byte (and arrays of)
- System.Char (and arrays of)
A property is mapped as a collection if it implements the System.IEnumerable interface. In this case all elements of the collection (both keys and values in the case of System.IDictionary) are stored in a special reference table.
Even though the EPiServer.Data.Dynamic.PropertyBag implements System.IEnumerable is will actually be treated as a reference type (see below).
All properties that cannot be mapped inline or as a collection (plus the EPiServer.Data.Dynamic.PropertyBag type) are mapped as references. This means that their properties are mapped in-turn as a sub-type, and a link row is added in the reference table to link the parent data structure with the child data structure. This allows for complex trees of data structures (object graphs) to be saved in the Dynamic Data Store.
The default “Big Table”
The default Dynamic Data Store “big table” is called tblBigTable, which contains the following fixed columns (meaning mandatory columns):
- pkId is the store ID and primary key of each data structure stored.
- Row is the row index. Each structure may span 1 or more rows in the “big table”.
- StoreName is the name of the store the data structure belongs to.
ItemType is the .NET CLR Type that contained the properties saved to the current row.
The default “big table” also contains the following optional columns:
- BooleanXX (where XX is 01 through to 05) x 5
- IntegerXX (where XX is 01 through to 10) x 10
- LongXX (where XX is 01 through to 05) x 5
- DateTimeXX (where XX is 01 through to 05) x 5
- GuidXX (where XX is 01 through to 03) x 3
- FloatXX (where XX is 01 through to 07) x 7
- StringXX (where XX is 01 through to 10) x 10
- BinaryXX (where XX is 01 through to 05) x 5
- Indexed_IntegerXX (where XX is 01 through to 03) x 3
- Indexed_LongXX (where XX is 01 through to 02) x 2
- Indexed_FloatXX (where XX is 01 through to 03) x 3
- Indexed_StringXX (where XX is 01 through to 03) x 3
- Indexed_Binary01 (not Oracle)
The columns whose name starts with “Indexed” have database indexes created on them.
Perhaps you want to add and remove columns in this table to suit the type of data you are saving. This may be particularly useful if you know you are going to store a data type with more than, for example, 10 strings. By default, the 11th to 20th strings would be stored in a 2nd row for the type which means a join has to be done at runtime when reading the data. By adding String11, String12 etc to the “big table”, you limit the chance of a row overspill and therefore increase performance. If you require more indexes then add columns with names starting with “Indexed” and ensure an index is created on them.
You can also add your own “big table” if you want. This may be particularly useful if you know you will be storing a type that only contains strings for example. Along with the mandatory columns (pkId, Row, StoreName, ItemType) you can add about 20 StringXX columns.
The following tables lists the database columns types in the default “big table” and the .NET CLR “inline” types they are mapped to:
SQL Server mappings
|Database Column Type||.NET CLR “Inline” Types|
|int||System.Byte, System.Int16, System.Int32, System.Enum|
|System.String, System.Char, System.Char, EpiServer.Data.Identity|
Storing database views
Each store is actually represented in the database by a view. The views can be used as normal including cross joining with other tables and views in the database.
Each data structure that is saved in the Dynamic Data Store is given an identity. This identity is represented by the EPiServer.Data.Identity class, which contains the following parts:
- System.Guid, which is an External ID that can either be supplied by the user of the Dynamic Data Store or generated dynamically.
- System.Int64, which is a Store ID that is always generated by the store.
Specific identity management
The implementer of a .NET class that is to be stored in the Dynamic Data Store can choose to explicitly manage the ID the objects get when stored. This can be done in the following ways:
- Implement the EPiServer.Data.Dynamic.IDynamicData interface
- Implement the EPiServer.Data.Entity.IEntity interface
- Implement a property called ‘ID’ which is of the type System.Guid
See the UsingManagedIdentity class in the DDS sample project for examples of specific identity management.
Implicit identity management (POCO support)
The Dynamic Data Store supports POCO objects. When POCO objects are stored in the Dynamic Data Store, special care needs to be taken when saving (updating) existing objects. Because the Dynamic Data Store does not have an ID it can use to determine if an object is new or existing, it relies on state information held for objects that have been previously loaded through the same instance of the Dynamic Data Store when saving them back.
See the UsingImplicitidentity class in the DDS sample project for examples of implicit identity management.
Dynamic Data Store has support for storing objects implementing IEntity, which can be used in the entity system (previously shipped with EPiServer Community/Relate). Classes implementing IEntity can use the same functionality provided to classes that implement IDynamicData (but the interfaces cannot be used at the same time).
Referencing an IEntity
An object, stored in the Dynamic Data Store with an IEntity property, will be stored in two different ways in depending if the IEntity has been registered to a provider or not.
If the IEntity has not been registered against a provider, the IEntity object will be stored in the same way as any other reference type used as a property.
If the IEntity has been registered to a provider, the property will be stored as a line in the reference table with property type, provider type, and the Identity (ID) value of the IEntity instance. When saving an object the IEntityProvider will not be called automatically for the property. Storing the property in its provider should therefore be done before calling the Save method on the main object.
When loading an object, the provider registered with the IEntity type will be called to load the property from its provider during load on the main object.
IEntity properties stored through a provider will not support LINQ. This is because the provider could store the objects in any possible way. It will still be possible to query on any other property in the class, but when querying on the IEntity property, a NotSupportedException will be thrown.
The Dynamic Data Store has extensive support for Microsoft’s Language Integrated Query (LINQ). The LINQ support is the same for both typed stores and for property bags.
“Where” is supported on inline types, independent if the inline types are directly on the queried object or nestled inside another object.
var query = (from person in _personStore.Items<Person>() where person.Address.City == "Stockholm" select person);