I was going to write this blog post for quite a while now. I’ve been exposed to the low level features of Sitecore on multiple occasions – building data providers, contributing to Sitecore.FakeDb, consulting project teams on various issues, and, of course, building SCORE. I believe that understanding fundamental concepts of Sitecore data architecture is critical to becoming proficient with the framework. Knowing how things work vs. how they appear to work changes the way you reason about the system and helps you architect and build your solutions right.
Items vs. Fields
Let me start with a few very simple but potentially controversial assertions:
[su_note note_color=”#fafafa”]#1. Items don’t have versions. Fields do.
#2. Items don’t have languages. Fields do.[/su_note]
Let’s take a closer look.
An item is represented by an ID
, a Template ID
, a Name
, its parent’s ID
, and a few other attributes:
So were do languages and versions live? The answer is – on the fields. As you probably know a field can be of one of three types:
Shared
– a field is defined by a fieldID
and can have a value. No variance per language and no versioning supportUnversioned
. Same as Shared plus a language dimension. A value of an Unversioned field can vary per content languageVersioned
. Same as Unversioned plus a version dimension. A value of a Version field can vary per content language and can have multiple values in each language – one per version number
How It Works
So what does it mean for an item to have three versions in English? It means that at least one field of this item is Versioned
and has values recorded for three different versions. The default SQL data provider basically runs a DISTINCT
on the fields to determine the list of versions an item has (or rather appears to have).
Do the languages also come back via DISTINCT
? Well, not exactly. An item’s unversioned and versioned fields can technically have a value in any language. That value will only make sense though when the Sitecore instance has that language registered as one of its content languages.
Let’s look at the APIs.
Asking a Database to return an item in a language it has no field values for will return… what exactly? Plausible answers are, for example, null
or an item object with no fields. Neither one is correct. By the way, a not-null
item always has fields (or may appear to have fields). It actually has any field you like. Or rather appears to have it. Don’t believe me? Try something like this:
var item = Factory.GetDatabase(“master”).GetItem(“/content”); if (item.Fields[ID.NewID] == null) { throw new Exception(“Told ya!”); }
Won’t fail. Why? Hold on! I will get back to it. Right now we’re back to the item that has no field values in a given language.
The item that has no field values in a language will be an item with no versions.
Here’s how you can tell in your code:
var item = Factory.GetDatabase(“master”).GetItem(id, language, Version.Latest); if (item.Versions.Count > 0) { // item (probably) has content in the language you specified // it at least has a version in that language // or rather has at least one versioned field with a value in that language }
Why is that? Let’s make a step back. Say you have just added a new content language into your instance. A language that no item has any content in. Sitecore will tell you that your items don’t have versions in that language and will offer you to create one. Do you know what happens when you click that link?
Here’s what it takes to create a new blank version (code simplified and formatted for the blog post):
private int AddBlankVersion(ItemDefinition item, Language language) { int num = this.GetLatestVersion(item, language) + 1; this.Api.Execute(" INSERT INTO VersionedFields (ItemId, Language, Version, FieldId, Value, Created, Updated) VALUES ({0}, {1}, {2}, {3}, {4}, {5}, {5} )", item.ID, language, num, FieldIDs.Created, DateUtil.IsoNowWithTicks, DateTime.UtcNow); return num; }
One field that is known to be Versioned
receives a value in that language. That’s it. All it takes. That’s where languages and versions become a thing called language version, simply because a field value in a version X is always in a language Y (Versioned
fields have two NOT NULL
dimensions – version and language).
[su_note note_color=”#fafafa”]UPDATE 05/14/2015:.
Creating a new version of an item will actually version all versioned fields with a not-null value in them.
A blank version will only be created if an item had no versions before. A most common use case is when you add a new content language and give your item its first version in that language. If an item already had a version the SQL data provider copies that version into the new one (simplified):
public override int AddVersion() { // ... if (baseVersion > 0) { this.CopyVersion(itemDefinition, baseVersion); } else { this.AddBlankVersion(itemDefinition, baseVersion.Language); } // ... }
You only really need to copy the versioned fields and that’s why the CopyVersion
method does INSERT INTO VersionedFields
.
[/su_note]
Versions
So what happens when we ask a database to give us an item in, say, current language and version 15? First, not all Versioned
fields will have a value in a given version. With versions being an attribute of a field those fields are all versioned somewhat independently.
[su_note note_color=”#fafafa”]I was not correct when I said fields are versioned independently and Sitecore grabs the latest version when a given version is not available. I stand corrected. Please read the update below.[/su_note]
[su_note note_color=”#fafafa”]UPDATE 05/14/2015:
A few examples when a field won’t have a value in a given version:
- Data template was modified and a new field was added. This field will have values starting with the version number that was the latest version at the time it was added.
- A versioned field was reset back to the standard values. Resetting a field translates to deleting that version from the
VersionedField
table. TheGetValue()
method on theField
object will then fall back to the standard value (more on that later in this post)
Even though versions live on the fields and not on the items, Sitecore does a pretty consistent job in keeping it all together. SqlDataProvider
keeps versioned fields separately and creates field lists (think snapshots) for each language/version combination. Add to that the CopyVersion
behavior and the answer to “give me an item of version 10” is “here you go, all versioned fields as of version 10”.
[/su_note]
Oh, and if you are writing a data provider (or an item provider) or scripting the creation of your items make sure you don’t create items with a version 0. The version number 0 is reserved to represent the latest version:
namespace Sitecore.Data { [Serializable] public class Version : ISerializable, ICacheable { private static readonly Version _first = new Version(1); private static readonly Version _invalid = new Version(-1); private static readonly Version _latest = new Version(0); // ... } }
I hope I cleared the languages and the versions a bit by now. If you are more confused than you were when you started reading it – don’t feel bad. Grasping these concepts and building your own mental model around it will help you in the future. Trust me, the more you understand the fundamentals – the better Sitecore developer you’ll become.
One things left. The fields.
Fields
[su_note note_color=”#fafafa”]While field values belong to an item, field definitions belong to a data template[/su_note]
The way it’s organized is actually very simple. The first thing I learned about Sitecore was that everything is an item. Fields are items too. A field item’s parent is a section. And the section item’s parent is a template item. Templates are items too.
When you ask Sitecore item for a field by ID, it immediately returns you a wrapper class:
public Field this[ID fieldID] { get { return new Field(fieldID, this._ownerItem); } }
An item is based on a data template and a template itself can inherit from multiple other templates. I guess it’s just easier and faster to return a wrapper object and defer actual field lookup until later when you need it plus a wrapper also helps to give out a standard value in case one of the item’s templates actually declares this field but the item just happens to have no value for it. It’s when you ask the field to give you a value when Sitecore will look at item’s InnerData
or defer to a StandardValuesManager
to do a standard value lookup.
Asking for a field by name is a little different:
public Field this[string fieldName] { get { ID fieldId = TemplateManager.GetFieldId(fieldName, this.TemplateId, this.Database); return fieldId == null? null : this[fieldId]; } }
Remember I mentioned how items only have field values? To look up the item’s field value by field name Sitecore needs to first fetch the field definition from the template to read its ID. This difference leads to an interesting opportunity. If you implement a custom standard values provider and always access your item fields by ID you can make your items have any kind of field you like. Using field names, however, is guarded by Sitecore to only return the fields declared on the item’s template(s).
Last but not least I wanted to mention the GetValue()
method on the Field
class. It’s a loaded method. Using the .Value
property:
var value = item.Fields["Title"].Value // or var value = item["Title"]
translates to calling the GetValue(true, true)
which will give you either the item’s value, or the value the item inherits from a source item if it’s a clone, or a standard value (or a default value that is stored in a Default value
field on the field item – this is legacy I believe, don’t use it).
There are times when you want to get the item’s own value or null
and skip the defaults:
var myValue = item.Fields["Title"].GetValue(false, false); // or if (item.Fields["Title"].HasValue) { // HasValue does this.GetValue(false, false) != null }
[su_divider][/su_divider]
There’s more. There’s a lot more to items, fields, versions, and languages and this blog post is already a few pages long. Some references before I go:
Read these and then take your Resharper / Reflector / ILSpy / dotPeek and dive in. Happy Exploring!
Missing this kind of article for long, must compliment the way you write it 🙂
Partially in agreement about fields having versions rather than items, but then how would Sitecore have version-level publishing restrictions?
I will update my post with an important amendment. Creating a new version of an item results in a version recorded for all versioned fields. This way there’s no confusion when it comes to publishing and no accidental side effects. I need to also explain how resetting a field back to its standard value in a later version of the item works. I guess I didn’t dig deep enough.
Thank you for your comment!