Chapter 2: Catalog content modeling

The strongly typed models

Now you can have the basic ideas of how the catalog is structured - it’s time to start working with some real code. As I mentioned above, you’ll be working with IContentRepository (in some cases, with its base interface - IContentLoader), around IContent. Any content must implement IContent to be handled by the content providers - so the first thing to get the Catalog integrated into the content system (aka to get CatalogContentProvider to work) is modelling the catalog, node, and entry with IContent

Let’s see how the catalog contents are modeled

Hierarchy of catalog content types, (forgive my drawing skills)
Hierarchy of catalog content types, (forgive my drawing skills)

All catalog content types inherit from CatalogContentBase.

RootContent is a “virtual” content - it has no Catalog counterpart. You can only have one RootContent in the system, itsContentLink is fixed and it is always read-only. You can try to save it but nothing will be persisted. You will hardly work with RootContent directly, but with its ContentReference, via ReferenceConverter.GetRootLink().

Let’s us see a simple code of creating a Catalog, then editing its language, then deleting it. (We are just showing how the API:s work)

 1     //Create a CatalogContent
 2     var catalogContent = contentRepository.GetDefault<Cat\
 3 alogContent>(referenceConverter.GetRootLink());
 4     catalogContent.Name = "Pro Episerver Commerce";
 5     catalogContent.DefaultCurrency = Currency.USD;
 6     catalogContent.DefaultLanguage = "en";
 7     catalogContent.WeightBase = "kgs";
 8     var catalogContentLink = contentRepository.Save(catal\
 9 ogContent, DataAccess.SaveAction.Publish, EPiServer.Secur\
10 ity.AccessLevel.NoAccess);
11 
12     //Edit it to add Swedish
13     catalogContent = contentRepository.Get<CatalogContent\
14 >(catalogContentLink).CreateWritableClone<CatalogContent>\
15 ();
16     catalogContent.CatalogLanguages.Add("sv");
17     contentRepository.Save(catalogContent, DataAccess.Sav\
18 eAction.Publish, EPiServer.Security.AccessLevel.NoAccess)\
19 ;
20 
21     //Delete it
22     contentRepository.Delete(catalogContentLink, false, E\
23 PiServer.Security.AccessLevel.NoAccess);

One thing to remember about CatalogContent is it’s different from NodeContent or EntryContentBase - it cannot be extended. While CatalogNode or CatalogEntry can be enriched by MetaClasses (as they implement IMetaClass interface), CatalogContent has no such abilities, you’ll always work with CatalogContent directly, never with its subclasses.

This will simply does not work:

1 [CatalogContentType(GUID = "1FD3E34D-6476-40E2-8CB5-8EFB0\
2 DB79E3E")]
3 public class MyCatalogContent : CatalogContent
4 {
5     public virtual string MyExtendedProperty { get; set; }
6 }

CatalogContentScannerExtension will throw an EPiServerException exception during the initialization if it detects any content types inherit from CatalogContent:

The content type ‘EPiServer.Commerce.Sample.MyCatalogContent’ extends ‘EPiServer.Commerce.Catalog.ContentTypes.CatalogContent’, which is not supported.

Working with Nodes and Entries is more or less the same. But you’ll have to model your metaclasses first.

Modeling catalog content types

It’s not required to model your metaclasses. You can use the defined classes such as NodeContent or ProductContent, and throw everything into Property properties, instead of content.VeryLongPropertyName, you can use content.Property["VeryLongPropretyName"]. (Remember? Any instance of IContent has Property property as a “property bag” to store its properties). It’s why Catalog UI (which relies on content types) will still work when you import a Catalog with metaclasses you didn’t model after. But that will defeat one of the most important benefit of content types: strongly typed types mean Intellisense and compile-time checking. Noticed the typo I intentionally made above? Visual Studio will catch that for you.

 1 /// <summary>
 2 /// FashionProduct class, map to Fashion_Product_Class me\
 3 tadata class
 4 /// </summary>
 5 [CatalogContentType(GUID = "18EA436F-3B3B-464E-A526-564E9\
 6 AC454C7", MetaClassName = "Fashion_Product", GroupName = \
 7 "Fashion",
 8     DisplayName = "Fashion product", Description = "fashi\
 9 on product with Add to Cart button.")]
10 [ImageUrl("~/Templates/thumbnail-fashion.png")]
11 public class FashionProduct : ProductContent
12 {
13     [Display(Name = "Description", Order = -15)]
14     public virtual XhtmlString Info_Description { get; se\
15 t; }
16 
17     [Display(Name = "Features", Order = -11)]
18     public virtual XhtmlString Info_Features { get; set; }
19 
20     [Display(Name = "Facet Brand", Order = -1)]
21     [Searchable]
22     public virtual string Brand { get; set; }
23 }

Catalog Content modeling uses attributes intensively. As you might see, there are plenty to explain:

  • CatalogContentType: this is the most important attribute. It inherits from CMS ContentType attribute, which makes this content type an option to select when you create new content in Catalog UI. GUID is something required by CMS to identify the content type, even its name and its namespace is changed. It has MetaClassName property indicate that your class is mapped to the corresponding metaclass. In case the metaclass does not exist GroupName groups your content type with other content types, so when you create a new content in CMS, they will in same group and be easier to select.
  • Other properties is to make the display of your content type in Catalog UI nicer and more recognizable.
  • ImageUrl thumbnail allow you to set a thumbnail to your content type.
How the attributes work in Catalog UI
How the attributes work in Catalog UI

When you define your strongly typed content, there are some rules to keep in mind:

  • The property must match the name and the type of the the metafield. Casing is not important. So if you have a string metafield named facet_color, your property must be string Facet_Color.
  • The property must be declared as virtual (An exception will be thrown if one or more properties are not declared virtual)
  • As a metafield can be used across multiple metaclass, the property which maps to it also has to be identical across content types, including all of the attributes decorated. That means you cannot have a property named Facet_Color as string in one class and another named Facet_Color as double in another class. Or Material which has CultureSpecific attribute in one content type, and without that property in another. If one of the properties has mismatched attributes across classes, an exception will be thrown during site initialization.
  • In most of the case, the underlying MetaDataType is automatically determined, so you don’t have to do anything else. This is, however, not true with dictionary types. Both of them require you to specify a BackingType. In case of MetaDataType.EnumSingleValue and MetaDataType.DictionarySingleValue, if you fail to do so, it’ll be created as a normal string, while MetaDataType.EnumMultiValue and MetaDataType.DictionaryMultiValue will throw exception during site startup.

Here’s how you create them:

1 [BackingType(typeof(PropertyDictionaryMultiple))]
2 public virtual IEnumerable<string> MultipleValue { get; s\
3 et; }
4 
5 [BackingType(typeof(PropertyDictionarySingle))]
6 public virtual string SingleValue { get; set; }

These are attributes you can use to decorate the property, which also map to attribute of a metafield:

  • Required: the property cannot be null
  • Searchable: the property will be indexed by the search provider
  • Tokenize: the value of the property will be tokenized while indexing. So if its value is “Hello world”, when Tokenize is set to true, then you can find this content with either “Hello” or “world”. Otherwise it will only be returned when you search for “Hello world”
  • IncludeValuesInSearchResults: Same as “Include value in search results” setting for metafield.
  • IncludeInDefaultSearch: same as “Include in default search” setting for metafield.
  • Encrypted: Indicates that the property will be saved to database (as a metafield) encrypted. This attribute, however, does not take effect, if your database is set to be Azure-compatible.
  • DecimalSettings: Allow you to set the precision and scale of a decimal type. Note that the highest precision of decimal type stored in SQL Server is (38,9), so anything bigger than that will not be allowed.

When your site starts, CatalogContentScannerExtension will do all the leg works to scan the content types and synchronize them to the metadata system - content types to metaclasses and and properties to metafields. In case of any conflicts between those pairs, catalog content types win. For example, you have a metafield with decimal precision of (18,0), but the mapping property has DecimalSettings to be (33,9), CatalogContentScannerExtension will update the metafield to be (33,9), not the way around.

IDimensionalStockPlacement

Most of properties of the base content types provided by Optimizely Commerce (CatalogContent, NodeContent, ProductContent, etc.) are flat, which means you can access directly, for example:

1 nodeContent.Code = "This-is-a-code";

But the contents implement IDimensionalStockPlacement (by default VariationContent and PackageContent), are exceptions. They are accessible via a block - ShippingDimensions.

1 variationContent.ShippingDimensions.Height = 10m;

That’s why the shipping dimensions are displayed a bit differently in Catalog UI:

Shipping dimensions as a block
Shipping dimensions as a block

If you have worked with CMS, you might already know that you don’t have to initialize a block before using it - it’ll never be null for a content. So you don’t have to write code such as:

1 variationContent.ShippingDimensions = new ShippingDimensi\
2 ons();

Just use the block directly, as we showed above.

The MetaData system

It’s hard to fully understand the Catalog system without knowing about the MetaData system, with its MetaClass(es) and MetaField(s). The Catalog is just the barebone, it’s the MetaData system which bring it to life.

The idea of metaclasses and metafields is simple. To make it effective to editing your catalog, you’ll need pre-defined templates. So a shirt needs a style (tailor fit, slim fit,…), a material (silk, cotton, polyester,…), a color (red, white, blue), a texture,…, while a TV needs a size (40”, 50”, 55”,…), a resolution (HD ready, FullHD or 4k), Smart features (Netflix, Youtube, Browser), 3D or not, etc. As a framework, Episerver cannot predefine all of those things out of the box - it’s up to you to define your “templates” for your needs. So each template is a metaclass, and each attribute of the template, is a metafield.

And each instance of the template, is a MetaObject. MetaObject is, in the simplified view, just a HashSet of values.

At API:s level, there’s nothing really interesting about MetaClass(es) or MetaField(s) in particular. Today, you will be hardly working with them - you should be working with the strongly typed models instead, which will come up in next part. However, they are still the internals of the catalog system, so it’ll make sense for us to explore how they works.

Let’s take a closer looks at MetaClass. At its core, it’s a simple class with some attributes (Id, Name, TableName, Namespace, etc) and a list of MetaFields attach to it. If you take a look at the MetaClass management in Commerce Manager (Commerce Manager/Administration/Catalog System/MetaClass), it’s as close as bare metal you can get (without looking at database, of course)

A MetaClass
A MetaClass

The key feature of MetaData system is the extensibility. You can attach or remove a metafield from a metaclass on-the-fly. You can share metafields between metaclasses.

It’s metafield which is more interesting

Create a MetaField
Create a MetaField

What do these settings mean?

  • Support multiple languages: That mean the metafield will be per-language, each language version of the MetaObject will have an unique value.
  • Use in comparing: This is a legacy setting which no longer has any meaning. (It should have been removed)
  • Allow Null Values: This metafield is not required.
  • Encrypted: The metafield will be encrypted by the master key in database and be decrypted in the fly. This option, however, does not show up if your database is set to be Azure-compatible. (As Sql Azure Database does not support encryption at the time of this writing).
  • Precision: This only set-able if your metafield is a decimal. This was important in the past because it will affect how the decimal is stored in the database, but with Commerce 9, the decimal will always be stored in the (38,9) form.

Search attributes: These attributes will affect how will this metafield be indexed in a Lucene-based search provider:

  • Allow Search: This metafield will be indexed by the search providers.
  • Enable Sorting Search results: The value of this metafield can be used for sort by the search providers.
  • Include Value in search results:
  • Tokenize: The value of this metafield will be tokenized. So if its value is “Hello World”, you can either search with “Hello” or “World”
  • Include in Default Search: This metafield will be include in the default Lucene query.

If you can only choose between either CatalogEntry or CatalogNode for MetaClass, then it’s whole lot more thing to choose for metafield. The full list of MetaType which you can choose from: {lang=C#} public enum MetaDataType { Integer = 26, Boolean = 27, Date = 28, Email = 29, URL = 30, ShortString = 31, LongString = 32, LongHtmlString = 33,

 1 	DictionarySingleValue = 34,
 2 	DictionaryMultiValue = 35,
 3 	EnumSingleValue		= 36,
 4 	EnumMultiValue		= 37,
 5 	StringDictionary	= 38,
 6 	File				=	39,
 7 	ImageFile			=	40,
 8 
 9 	MetaObject			=	41
10 }

The only caveat of choosing a type is that you can’t change the type of a metafield after you saved it. So if you make a wrong move, it might be easiest to just delete the metafield and create again.

Then how MetaObjects are stored?

Here’s the map between PropertyData types and MetaField types:

MetaDataType PropertyData type
MetaDataType.Decimal PropertyDecimal
MetaDataType.BigInt  
MetaDataType.Money  
MetaDataType.SmallMoney  
MetaDataType.DateTime PropertyDate
MetaDataType.SmallDateTime  
MetaDataType.Date  
MetaDataType.Float PropertyFloatNumber
MetaDataType.Real  
MetaDataType.Bit PropertyNumber
MetaDataType.Char  
MetaDataType.Int  
MetaDataType.Integer  
MetaDataType.SmallInt  
MetaDataType.TinyInt PropertyNumber
MetaDataType.NChar PropertyLongString
MetaDataType.VarChar  
MetaDataType.NText  
MetaDataType.Text  
MetaDataType.NVarChar  
MetaDataType.LongString  
MetaDataType.Boolean PropertyBoolean
MetaDataType.Email PropertyEmailAddress
MetaDataType.URL PropertyUrl
MetaDataType.ShortString PropertyString
MetaDataType.UniqueIdentifier  
MetaDataType.LongHtmlString PropertyXhtmlString
MetaDataType.EnumMultiValue PropertyDictionaryMultiple
MetaDataType.DictionaryMultiValue  
MetaDataType.EnumSingleValue PropertyDictionarySingle
MetaDataType.DictionarySingleValue  
MetaDataType.Timestamp Unsupported
MetaDataType.Variant  
MetaDataType.Numeric  
MetaDataType.Sysname  
MetaDataType.Binary  
MetaDataType.VarBinary  
MetaDataType.StringDictionary  
MetaDataType.Image  
MetaDataType.File  
MetaDataType.ImageFile  
MetaDataType.MetaObject  

How properties are stored

It might not be particularly useful to look into how properties are stored in database, but it can be interesting to know the detail (You might someday look into your databases to investigate, when a problem arises.)

In Commerce 9, the way properties are stored is fundamentally changed.

If you have worked with CMS content properties before, you might know about tblContentProperties. Commerce 9 adapts the same idea.

CatalogContentProperty table, it's new in Commerce 9
CatalogContentProperty table, it’s new in Commerce 9

Commerce 9 has a new table - CatalogContentProperty to store property. Besides the identity columns, there are 10 columns for the datatypes MetaDataPlus supports: Boolean, Number (Integer), FloatNumber, Money, Decimal [^foo151], Date, Binary, String (for strings shorter than 256 characters length), LongString (any strings long than that) and Guid. A metafield, for each content and for each language (in case it’s culture specific) will be stored in one row matches with it value. So a string property value will be in String column, while an Integer property value will be in Number column, and so on and so forth, while the other columns remain null for that row.

It’s hard to say if the old or new approach is definitely better than the other. The new one might be more flexible, but it means more rows and is not as “natural” as the old one. However, the new one has simply access approach - you only read or update or delete from one table, while the old one has to rely on the automatically generated stored procedures (which are updated every time you add or remove metafields to metaclasses). In long run, the new approach might be simpler to maintain. Let’s hope for that.

When you upgrade your site to Commerce 9, all old metaclass tables are dropped. Of course, it does not really make sense to keep the tables you no longer update. However, you might still have custom queries to work on that tables (such as to report something on your catalog). To replace those tables, new views are created. They are created by querying the data from CatalogContentProperty table, and are updated every time you update your metaclasses (just like the way old metaclass tables did).

The new views to replace metaclass tables
The new views to replace metaclass tables

Those views are, as you can expect, slower than the original tables, some tests shows that they can be 30% slower than the original tables. However, you’ll not use them frequently enough for the difference to really make senses.

Dictionary types.

Previously we discussed on how properties work with catalog content. However - if you have dictionary types in your MetaClasses, they will work differently. In this section we will examine these special data types - this applies to Order system metaclasses as well.

As we all know - there are three types of dictionary in Episerver Commerce:

  • Single value dictionary: editor can select a value from defined ones.
In Commerce Manager, you can create new metafield with type of Dictionary, but without "Multiline" option
In Commerce Manager, you can create new metafield with type of Dictionary, but without “Multiline” option

Single value dictionary type is supported in the strongly typed content types - you’ll need to define a property of type string, with backing type of typeof(PropertyDictionarySingle)

1     [BackingType(typeof(PropertyDictionarySingle))]
2     public virtual string Color { get; set; }
  • Multi value Dictionary: editor can select multiple values from defined ones. The only different from Single value dictionary is it has the “Multiline” option enabled.

You can define a property for Multi value dictionary in content type by IEnumeable<string> and typeof(PropertyDictionaryMultiple) backing type

1     [BackingType(typeof(PropertyDictionaryMultiple))]
2     public virtual IEnumerable<string> Colors { get; set;\
3  }

Both single and multi value dictionary types are fully supported in Catalog UI, including editing:

In Catalog UI, a dictionary will be rendered into this, so make sure to not have too many options and administration:

Manage values for a dictionary field in Settings view
Manage values for a dictionary field in Settings view
  • String dictionary: this is the true “dictionary type”: you can define pairs of key and value (the previous types are actually “list”).

String dictionary is not supported by the strongly typed content types, nor Catalog UI. To manage this metafield, we’ll have to use Commerce Manager, or use MetaDataPlus API:s

Pairs of key and value can be managed directly in CatalogEntry edit view in Commerce Manager
Pairs of key and value can be managed directly in CatalogEntry edit view in Commerce Manager

In later chapters, we will see how to add the support for it in Catalog UI.

How dictionary works

We’ve learned in previous chapters how properties are stored and loaded. Technically, dictionaries are “just another properties”. However they are stored differently. If you look at CatalogContentProperty table (or ecfVersionProperty, for draft versions), you’ll see the dictionary properties are stored as numbers. What do those numbers mean?

  • For single value dictionary type, that number is the MetaDictionaryId of the selected value in MetaDictionary table.
  • For multi value dictionary type, things are a bit more complicated. That number is the MetaKey value in MetaKey table. This MetaKey, is, however, connected to MetaMultiValueDictionary, which itself points back to MetaDictionary. An “usual” design for 1-n relation in database, right?
  • For string dictionary type, it’s more or less the same as multi value dictionary. However, the MetaKey will point to pairs of key and value in MetaStringDictionaryValue table.