How to store custom data in Episerver – Part I: Dynamic Data Store basics

When you work with Episerver you might need to store extra data in your database. A simple example would be when you want to save statistics of your page views. You could think: “Hey! I can have another hidden property on my PageData object and store page views count in it.”

Let me stop you there. Episerver offers a better and at the same time a very easy way to store some custom data which is not supposed to be seen or edited by CMS users. The solution is Dynamic Data Store (DDS).

Dynamic data store is an Object-Relational Mapper (ORM) which means it can convert data between a database and a C# code. Without further explanation, let’s see how you can actually use it in your code and let’s go back to the example in which you’d like to store page views data.

Dynamic data store definition

using EPiServer.Data;
using EPiServer.Data.Dynamic;
using System;

namespace Setapp.DataStore
{
    [EPiServerDataStore(AutomaticallyCreateStore = true, AutomaticallyRemapStore = true)]
    public class PageViewsData
    {
        public Identity Id { get; set; }

        [EPiServerDataIndex]
        public int PageId { get; set; }

        public int ViewsAmount { get; set; }
    }
}

The class represents a model of data you want to store. The class must have:

    • an attribute EPiServerDataStore
    • a property of type Identity from namespace EPiServer.Data which also needs to be named Id,
    • other custom properties which will be saved in a database (in this example you will need a property to store an ID of a page and also a property to store a number of views that this page has).

Episerver documentation doesn’t list all types that you can actually use. However here’s what it includes:

    • System.Byte
    • System.Int16
    • System.Int32
    • System.Int64
    • System.Enum
    • System.Single
    • System.Double
    • System.DateTime
    • System.String
    • System.Char
    • System.Boolean
    • System.Guid
    • EPiServer.Data.Identity
    • System.IEnumerable
    • System.IDictionary

But you can also use properties of other types, for example:

    • EPiServer.Core.ContentReference,
    • EPiServer.Core.IContent.

So why are we using int instead of ContentReference to store ID of a page? ContentReference is mapped into a column of type string so searching by it is slower than by an integer value.

Although you can use also IContent to store the whole object, I wouldn’t recommend it since actually every property value of IContent object is stored separately in a table so that’s not very efficient.

On application start, Episerver will find the class by the attribute and create a Dynamic Data Store, which means it will map properties to table columns in a database and also save information about the mapping and the store itself in some other Episerver tables.

Thanks to setting AutomaticallyCreateStore and AutomaticallyRemapStore properties to true in EPiServerDataStore attribute, Episerver will automatically create or update the store definition if you change your class definition.

Notice that you can also set up indexes on chosen columns. In our example, you will probably search for views data by the page ID so it’s a good idea to set a database index on the ID column to improve the code performance. You can do it by simply adding EPiServerDataIndex to the PageID property.

How to save and read Dynamic data store data

Ok, so we already have the store definition but how to use that? Let’s start with how to save some data first. In our case if a user visits a page for the first time, you want to add the first record about the page view. First, you need to get an instance of the data store. You can either get it by using an extension method:

DynamicDataStore store = typeof(PageViewsData).GetOrCreateStore();

or by using DynamicDataStoreFactory object:

dynamicDataStoreFactory.GetOrCreateStore(typeof(PageViewsData));

In the latter solution you can get dynamicDataStoreFactory in two ways: either by referencing to a static field in DynamicDataStoreFactory class (DynamicDataStoreFactory.Instance) or by using a dependency injection in your class constructor.

What’s the difference here? Using the extension method seems to be the easiest way to go. However, using the dependency injection allows you to write code which is more testable because you can then create mocks for your stores. But that’s a different story, so, for now, let’s focus on the easier solution.

Once you have your store instance and a PageData object called page, you need to create an object of PageViewData:

var viewData = new PageViewsData
{
    PageId = page.ContentLink.ID,
    ViewsAmount = 1
};

Then you can finally save the data by simply calling:

store.Save(viewData);

And that’s it! Episerver automatically fills in the ID field, so you don’t need to care about that.

But what if the page is being visited for the second time and you just want to increase the view counter for a page?

 “Read and write” approach

Given the same page object as in the previous example, you can first read a number of views. To do so, you need to call a generic method Items and then you can use LINQ queries to find objects with some criterias.

PageViewData pageViewData = store.Items<PageViewsData>()
    .Where(viewData => viewData.PageId == page.ContentLink.ID)
    .FirstOrDefault(); 

if (pageViewData == null)
{
    pageViewData = new ViewData
    {
        ContentId = page.ContentLink.ID,
        ViewsAmount = 0
    };
} 

pageViewData.ViewsAmount++;
store.Save(pageViewData);

You try to find an already existing record about the page. If there is none, you create a new object like in the previous example. Then, you increase the views amount and also just pass the object to store to save it. If the object is retrieved by the data store, it will have an Id field filled in, so when you update its properties and save it, DDS will know which record to update.

But this approach has some disadvantages. First of all, there is a risk that another request will appear and perform the same read and write on the updated record between our read and write operations. So, in this case, the counter wouldn’t be reliable as it would be increased only by 1, even though there were 2 requests to the same page. So what you can do here is use DDS transactions. You can wrap the whole code from above with a transaction:

DynamicDataStore store = typeof(PageViewsData).GetOrCreateStore();
var provider = DataStoreProvider.CreateInstance();

provider.ExecuteTransaction(() =>
{
    store.DataStoreProvider = provider;
    //here goes the code for updating the page view
});

You can even share the DataStoreProvider between many different stores in the same transaction if you want to keep your data consistent in many data stores!

“Just update” approach

In the previous example, you base the new value on the old one. But if you already know what the new value should be, there is a better way to update store items. The previous approach works fine as long as you don’t need to update many records in the same time. If you do, then in the first step all the objects need to be loaded to RAM.

If you work with big tables, that can get problematic and it can also slow down the whole operation a lot. So instead you can simply tell the store which records to update and what values to change!

So let’s say we just want to set the views’ amount to 10:

store.Update<PageViewsData>()
    .Where(viewData => viewData.PageId == page.ContentLink.ID)
    .Set(viewData => viewData.ViewsAmount, 10)
    .Execute();

In this case all rows matching the Where query will be updated with new properties values set in the Set function. And it’s all translated to one SQL update query, so it’s much faster and also transactional! Much nicer, huh?

Conclusions

The implemented Dynamic Data Store solution is for sure very flexible since a store can be automatically rebuilt on most class changes like adding a new property. It’s good for storing a small amount of objects and it’s very fast to implement.

Unfortunately, the Episerver solution has many serious drawbacks. You can learn more about that and get information about implementation details in Part II of this article. Coming very soon!

 

episerver cms

 

Let's create
something meaningful together!

Company data
  • Setapp Sp. z o.o.
  • VAT ID: PL7781465185
  • REGON: 301183743
  • KRS: 0000334616
Address
  • ul. Wojskowa 4
  • 60-792 Poznań, Poland
Contact us
Stay connected