LibrarySites.Banner

Sitecore 7 POCO's explained

What your POCO's can do in Sitecore 7

Key Takeaway's

  • We support all primitive types, IEnumberable<T>, IList<T>, ICollection<T>, ItemUri, ID, DateTimeOffset, TimeSpan out of the box.
  • In most cases, all your POCO's will inherit from SearchResultItem, which brings with it all the goodies such as TemplateId, TemplateName, Name, CreatedBy, CreatedDate etc.
  • We have a framework to implement TypeConverters to support other types.
  • You have available attributes to mark up your class for IndexField Mapping and TypeConverting and in later updates we will bring Filters at the class level and IgnoreFields.
  • To be a candidate for document mapping your property will need to be public, with an empty set;
  • You can wrap properties over IndexField mappings e.g. instead of returning Language as a string you can wrap it to be of type Language or build a TypeConvertor for the Language class (coming out of the box in Update 2)
  • Currently, it is up to you to create the classes, we don't do auto generation for you based off the Sitecore Template.

One of the very powerful features of Sitecore 7 is the ability to have your POCO's mapped automatically for you to the index (it doesn't map to the database or other sources - although you do have handy methods available to fetch the Item or Field or FieldCollection from the database based off an indexed item). We really want to see developers in Sitecore thinking about putting more into their index, as much as they need, as this will always make for the fastest retrieval system we have available. In most cases you can substitute database retrieval with index retrieval for getting back the data you need, e.g. Item Url, Item Fields, Creation Date, Template, that information all belongs in the index, so you can query on it and have your POCO's mapped to the index values.

A really simple example looks like this:

public class Article 
{
    public string ArticleSummary {get;set;}
}

This example above is the simplest form of creating a POCO that is consumable by the LINQ layer within Sitecore. Next, we can jazz it up by inheriting from SearchResultItem.

public class Article : SearchResultItem 
{
    public string ArticleSummary {get;set;}
}

In doing so we now have the ArticleSummary property available as well as properties like Name, Language, Version etc. SearchResultItem is intended as a base class to give you common properties you will want on most of your types. Next we add a complex type.

public class Article : SearchResultItem 
{
    public string ArticleSummary {get;set;}

    //Map multivalued fields
    public IEnumerable<Article> RelatedArticles {get; set;}
}

Believe it or not, Sitecore knows how to map this to your custom type to retrieve multiple values for the same field from the index. This would be useful for mapping fieldtypes such as MultiList, MultiList with Search etc. Next we can evolve the POCO with a property that needs to translate the IndexField name instead of relying on the auto-mapping that we will innately do on the property name. EDIT: The code below requires that you have built a TypeConvertor for the Article class to tell the DocumentMapper how to store the Article class in the index as well as fetch it as well. If you do not want to do this then you can just use IEnumerable<ID> as we already have a TypeConvertor for this.

public class Article : SearchResultItem 
{
    public string ArticleSummary {get;set;}

    public IEnumerable<Article> RelatedArticles {get; set;}

    // The field in the index is actually called pubdate
    [IndexField("pubdate")]
    public DateTime PublishingDate {get;set;}
}

The Document Mapper will know that when it is filling in the values of the POCO, it needs to get the values from a field in the index called "pubdate" instead of doing a map on the exact property name - PublishingDate. If by chance you do forget to put the IndexField attribute mapping in, then your property value will simply be null. In this example we have a very specific Template i.e. Article. This will not always be the case, sometimes you will want to work with a very generic Template e.g. BaseItem. Because we are dealing with a specific Template we can add an attribute to the class so that the Linq layer will only bring back results based off the Template.

UPDATE: In Update 1 we are planning to introduce the following support out of the box

  • Nested Types
  • PredefinedQuery
  • IgnoreFields

You will see some features in the next part that are not currently available but are planned to come in either Update 1 or 2. We are showing this so you can plan for your development.

[PredefinedQuery("template", ComparisonType.Equal, "{3B4DE798-2B1A-4D1F-8AA7-1ED3742399CB}", typeof(GUID))]
public class Article : SearchResultItem 
{
    public string ArticleSummary {get;set;}

    public IEnumerable<Article> RelatedArticles {get; set;}

    // The field in the index is actually called pubdate
    [IndexField("pubdate")]
    public DateTime PublishingDate {get;set;}
}

Next we can see that we can map the IndexField with Constants, not just strings. We will map an ArticleExternalID property in this way.

[PredefinedQuery("template", ComparisonType.Equal, "{3B4DE798-2B1A-4D1F-8AA7-1ED3742399CB}", typeof(GUID))]
public class Article : SearchResultItem 
{
    public string ArticleSummary {get;set;}

    public IEnumerable<Article> RelatedArticles {get; set;}

    // The field in the index is actually called pubdate
    [IndexField("pubdate")]
    public DateTime PublishingDate {get;set;}

    [IndexField(Settings.KnownNames.ArticleExternalID)]
    public GUID ArticleExternalID {get;set}
}

I can now add properties that I don't want the document mapping system to try and map from the index. For example, let's say I have a property with just a get and a set but I don't want the index mapping to even try and map it.

[PredefinedQuery("template", ComparisonType.Equal, "{3B4DE798-2B1A-4D1F-8AA7-1ED3742399CB}", typeof(GUID))]
public class Article : SearchResultItem 
{
    public string ArticleSummary {get;set;}

    public IEnumerable<Article> RelatedArticles {get; set;}

    // The field in the index is actually called pubdate
    [IndexField("pubdate")]
    public DateTime PublishingDate {get;set;}

    [IndexField(Settings.KnownNames.ArticleExternalID)]
    public GUID ArticleExternalID {get;set}

    [IgnoreIndexField]
    public bool IsPublished {get;set;}
}

Let's add one more thing, which is to have strongly typed mappings over the Language property that comes from the SearchResultItem.

[PredefinedQuery("template", ComparisonType.Equal, "{3B4DE798-2B1A-4D1F-8AA7-1ED3742399CB}", typeof(GUID))]
public class Article : SearchResultItem 
{
    public string ArticleSummary {get;set;}

    public IEnumerable<Article> RelatedArticles {get; set;}

    // The field in the index is actually called pubdate
    [IndexField("pubdate")]
    public DateTime PublishingDate {get;set;}

    [IndexField(Constants.Author)]
    public string Author {get;set;}

    [IndexField(Settings.KnownNames.ArticleExternalID)]
    public GUID ArticleExternalID {get;set}

    [IgnoreIndexField]
    public bool IsPublished {get;set;}

    [IgnoreIndexField]
    public Language LanguageWrapper 
    {
        get { return LanguageManager.GetLanguage(this.Language); } 
    }

}

UPDATE: In Update 2 we plan on introducing the following type converter support out of the box

  • Database
  • Language
  • Version
  • CultureInfo

Remember that at the end of the day this is still just a normal class, so adding helper methods is also possible like we have done with the GetChildren method below.

[PredefinedQuery("template", ComparisonType.Equal, "{3B4DE798-2B1A-4D1F-8AA7-1ED3742399CB}", typeof(GUID))]
public class Article : SearchResultItem 
{
    public string ArticleSummary {get;set;}

    public IEnumerable<Article> RelatedArticles {get; set;}

    // The field in the index is actually called pubdate
    [IndexField("pubdate")]
    public DateTime PublishingDate {get;set;}

    [IndexField(Constants.Author)]
    public string Author {get;set;}

    [IndexField(Settings.KnownNames.ArticleExternalID)]
    public GUID ArticleExternalID {get;set}

    [IgnoreIndexField]
    public bool IsPublished {get;set;}

    [IndexField("_databasename")]
    Database DatabaseInstance {get;set;}

    //Do not need a mapper for this anymore in Update 2 
    [IndexField("_language")]
    public Language Language {get;set;}

    //Lazy Loaded Sub-queries
    public IQueryable<TResult> GetChildren<TResult>(IProviderSearchContext context) where TResult : SearchResultItem, new()
    {
        Item sitecoreItem = this.GetItem();
        var query = context.GetQueryable<TResult>(new CultureExecutionContext(new CultureInfo(this.Language))).Where(i => i.Parent == sitecoreItem.ID);
        return query;
    }
}

This summarizes the default implementation that we have in the Sitecore Document Mapper. We have left so many places open for you to inject your own work into to do fancy things with the POCO mapping. We have already been blown away by seeing two document mappers already not only have support for Sitecore 7 but enriching it with new features and taking different approaches.

Dev Team